/*-------------------------------------------------------------------------
 *
 * postgres.c
 *	  POSTGRES C 后端接口
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/tcop/postgres.c
 *
 * NOTES
 *	  这是 postgres 后端的“主”模块，因此也是“交通警察”的主模块。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/time.h>
#include <sys/resource.h>
#endif

#ifndef HAVE_GETRUSAGE
#include "rusagestub.h"
#endif

#include "access/parallel.h"
#include "access/printtup.h"
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "common/pg_prng.h"
#include "jit/jit.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
#include "mb/pg_wchar.h"
#include "mb/stringinfo_mb.h"
#include "miscadmin.h"
#include "nodes/print.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parser.h"
#include "pg_getopt.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/logicalworker.h"
#include "replication/slot.h"
#include "replication/walsender.h"
#include "rewrite/rewriteHandler.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
#include "utils/snapmgr.h"
#include "utils/timeout.h"
#include "utils/timestamp.h"
#include "utils/varlena.h"
#ifdef FDD
#include "parser/parse_expr.h"
#endif
/* ----------------
 *		全局变量
 * ----------------
 */
const char *debug_query_string; /* 客户端提供的查询字符串 */

/* 注意：whereToSendOutput 为引导/独立情况初始化 */
CommandDest whereToSendOutput = DestDebug;

/* 会话结束的日志标志 */
bool		Log_disconnections = false;

int			log_statement = LOGSTMT_NONE;

/* GUC 变量表示最大堆栈深度（以千字节为单位） */
int			max_stack_depth = 100;

/* 等待 N 秒以允许从调试器附加 */
int			PostAuthDelay = 0;

/* 检查客户端是否仍然连接之间的时间。 */
int			client_connection_check_interval = 0;

/* 用于限制非系统关系类型使用的标志 */
int			restrict_nonsystem_relation_kind;
#ifdef FDD
//该hook用于异常退出、打印error日志等时，对审计和入侵的处理，在PostgresMain中调用
FDB_TypeNotifyEventHook fdb_notify_event_hook = NULL;
FDB_TypeGenerateAuditEventHook fdb_generate_audit_event_hook = NULL;
#endif

/* ----------------
 *		私有类型定义等
 * ----------------
 */

/* 绑定参数错误回调的参数类型 */
typedef struct BindParamCbData
{
	const char *portalName;
	int			paramno;		/* 从零开始的参数编号，或最初为 -1 */
	const char *paramval;		/* 文本输入字符串，如果可用 */
} BindParamCbData;

/* ----------------
 *		私有变量
 * ----------------
 */

/* 为了快速检查而转换为字节的最大堆栈深度 */
static long max_stack_depth_bytes = 100 * 1024L;

/* 
 * 堆栈基指针 -- 由 PostmasterMain 初始化并由
 * 子进程继承（但请参阅 InitPostmasterChild）。
 */
static char *stack_base_ptr = NULL;

/* 
 * 在 IA64 上，我们还必须记住寄存器堆栈基址。
 */
#if defined(__ia64__) || defined(__ia64)
static char *register_stack_base_ptr = NULL;
#endif

/* 
 * 标志以跟踪我们是否已开始事务。
 * 对于扩展查询协议，这必须跨消息记住。
 */
static bool xact_started = false;

/* 
 * 标志表示我们正在进行外循环的从客户端读取，
 * 与在 COPY FROM STDIN 等命令内可能发生的任何随机从客户端读取相对。
 */
static bool DoingCommandRead = false;

/* 
 * 标志实现扩展查询协议消息在错误后跳过到同步的行为。
 */
static bool doing_extended_query_message = false;
static bool ignore_till_sync = false;

/* 
 * 如果存在未命名的准备语句，它存储在这里。
 * 我们将其与 commands/prepare.c 中保持的哈希表分开，
 * 以减少短期查询的开销。
 */
static CachedPlanSource *unnamed_stmt_psrc = NULL;

/* 各种命令行开关 */
static const char *userDoption = NULL;	/* -D 开关 */
static bool EchoQuery = false;	/* -E 开关 */
static bool UseSemiNewlineNewline = false;	/* -j 开关 */

/* 我们是否被恢复冲突取消，以及原因 */
static bool RecoveryConflictPending = false;
static bool RecoveryConflictRetryable = true;
static ProcSignalReason RecoveryConflictReason;

/* 重新使用的缓冲区传递给 SendRowDescriptionMessage() */
static MemoryContext row_description_context = NULL;
static StringInfoData row_description_buf;

/* ----------------------------------------------------------------
 *		此文件中仅使用的例程的声明
 * ----------------------------------------------------------------
 */
static int	fc_InteractiveBackend(StringInfo fc_inBuf);
static int	fc_interactive_getc(void);
static int	fc_SocketBackend(StringInfo fc_inBuf);
static int	fc_ReadCommand(StringInfo fc_inBuf);
static void fc_forbidden_in_wal_sender(char fc_firstchar);
static bool fc_check_log_statement(List *fc_stmt_list);
static int	fc_errdetail_execute(List *fc_raw_parsetree_list);
static int	fc_errdetail_params(ParamListInfo fc_params);
static int	fc_errdetail_abort(void);
static int	fc_errdetail_recovery_conflict(void);
static void fc_bind_param_error_callback(void *fc_arg);
static void fc_start_xact_command(void);
static void fc_finish_xact_command(void);
static bool fc_IsTransactionExitStmt(Node *fc_parsetree);
static bool fc_IsTransactionExitStmtList(List *fc_pstmts);
static bool fc_IsTransactionStmtList(List *fc_pstmts);
static void fc_drop_unnamed_stmt(void);
static void fc_log_disconnections(int fc_code, Datum fc_arg);
static void fc_enable_statement_timeout(void);
static void fc_disable_statement_timeout(void);

#ifdef FDD
#define PASSWORD_RULE_NONE 0 //不做密码复杂度验证 
#define FDB_PASSWORD_RULE_CONF "fdb.password_rule"
static int fdb_authPasswordRule = -1;
static const char *fdb_mask_password(const char *fc_sql);
static void FDB_Update_PasswordRule_Conf(void);
#endif //FDD

/* ----------------------------------------------------------------
 *		获取用户输入的例程
 * ----------------------------------------------------------------
 */

/* ----------------
 *	InteractiveBackend() 被调用以进行用户交互连接
 *
 *	用户输入的字符串放在参数 inBuf 中，
 *	我们表现得就像接收到了一条 Q 消息。
 *
 *	如果看到文件结束输入，则返回 EOF；该关闭时间到。
 * ----------------
 */

static int fc_InteractiveBackend(StringInfo fc_inBuf)
{
	int			fc_c;				/* 从 getc() 读取的字符 */

	/* 
	 * 显示提示并从用户那里获取输入
	 */
	printf("backend> ");
	fflush(stdout);

	resetStringInfo(fc_inBuf);

	/* 
	 * 读取字符直到看到 EOF 或适当的定界符。
	 */
	while ((fc_c = fc_interactive_getc()) != EOF)
	{
		if (fc_c == '\n')
		{
			if (UseSemiNewlineNewline)
			{
				/* 
				 * 在 -j 模式下，分号后跟两个换行符结束命令；
				 * 否则将换行符视为常规字符。
				 */
				if (fc_inBuf->len > 1 &&
					fc_inBuf->data[fc_inBuf->len - 1] == '\n' &&
					fc_inBuf->data[fc_inBuf->len - 2] == ';')
				{
					/* 也可以删除第二个换行符 */
					break;
				}
			}
			else
			{
				/* 
				 * 在普通模式下，换行符结束命令，除非前面有反斜杠。
				 */
				if (fc_inBuf->len > 0 &&
					fc_inBuf->data[fc_inBuf->len - 1] == '\\')
				{
					/* 从 inBuf 中丢弃反斜杠 */
					fc_inBuf->data[--fc_inBuf->len] = '\0';
					/* 也丢弃换行符 */
					continue;
				}
				else
				{
					/* 保持换行符，但结束命令 */
					appendStringInfoChar(fc_inBuf, '\n');
					break;
				}
			}
		}

		/* 不是换行符，或者换行符被视为常规字符 */
		appendStringInfoChar(fc_inBuf, (char) fc_c);
	}

	/* 在EOF信号之前没有输入意味着是时候退出了。 */
	if (fc_c == EOF && fc_inBuf->len == 0)
		return EOF;

	/*
	 * 否则我们有用户查询，所以处理它。
	 */

	/* 添加 '\0' 使其看起来与消息案例相同。 */
	appendStringInfoChar(fc_inBuf, (char) '\0');

	/*
	 * 如果给出了查询回显标志，则打印查询。
	 */
	if (EchoQuery)
		printf("statement: %s\n", fc_inBuf->data);
	fflush(stdout);

	return 'Q';
}

/*
 * interactive_getc -- 从stdin收集一个字符
 *
 * 即使我们不是从“客户端”进程中读取，我们仍然希望
 * 响应信号，特别是SIGTERM/SIGQUIT。
 */
static int fc_interactive_getc(void)
{
	int			fc_c;

	/*
	 * 这在读取时不会处理补充中断或通知。
	 * 但这些对独立后端来说实际上并不相关。要正确处理SIGTERM，
	 * 在die()中有一个黑客，它在此阶段直接处理中断...
	 */
	CHECK_FOR_INTERRUPTS();

	fc_c = getc(stdin);

	ProcessClientReadInterrupt(false);

	return fc_c;
}

/* ----------------
 *	SocketBackend()		用于前端-后端连接
 *
 *	返回消息类型代码，并将消息主体数据加载到inBuf中。
 *
 *	如果连接丢失，则返回EOF。
 * ----------------
 */
static int fc_SocketBackend(StringInfo fc_inBuf)
{
	int			fc_qtype;
	int			fc_maxmsglen;

	/*
	 * 从前端获取消息类型代码。
	 */
	HOLD_CANCEL_INTERRUPTS();
	pq_startmsgread();
	fc_qtype = pq_getbyte();

	if (fc_qtype == EOF)			/* 前端已断开连接 */
	{
		if (IsTransactionState())
			ereport(COMMERROR,
					(errcode(ERRCODE_CONNECTION_FAILURE),
					 errmsg("unexpected EOF on client connection with an open transaction")));
		else
		{
			/*
			 * 此时无法向客户端发送DEBUG日志消息。由于
			 * 我们立即断开连接，因此不需要恢复
			 * whereToSendOutput。
			 */
			whereToSendOutput = DestNone;
			ereport(DEBUG1,
					(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
					 errmsg_internal("unexpected EOF on client connection")));
		}
		return fc_qtype;
	}

	/*
	 * 在尝试读取主体之前验证消息类型代码；如果我们失去了
	 * 同步，最好说“命令未知”，而不是因为
	 * 我们使用了垃圾作为长度词而耗尽内存。我们还可以选择一个类型相关的
	 * 限制来定义一个合理的长度词可能是什么。（这个限制可以选择得更细粒度，
	 * 但不清楚它是否值得费心。）
	 *
	 * 这也给我们一个地方来尽快设置doing_extended_query_message标志。
	 */
	switch (fc_qtype)
	{
		case 'Q':				/* 简单查询 */
			fc_maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
			doing_extended_query_message = false;
			break;

		case 'F':				/* 快路径函数调用 */
			fc_maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
			doing_extended_query_message = false;
			break;

		case 'X':				/* 终止 */
			fc_maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
			doing_extended_query_message = false;
			ignore_till_sync = false;
			break;

		case 'B':				/* bind */
		case 'P':				/* parse */
			fc_maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
			doing_extended_query_message = true;
			break;

		case 'C':				/* close */
		case 'D':				/* describe */
		case 'E':				/* execute */
		case 'H':				/* 刷新 */
			fc_maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
			doing_extended_query_message = true;
			break;

		case 'S':				/* sync */
			fc_maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
			/* 停止任何活动的跳过直到同步 */
			ignore_till_sync = false;
			/* 标记为未扩展，以便新的错误不会开始跳过 */
			doing_extended_query_message = false;
			break;

		case 'd':				/* 复制数据 */
			fc_maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
			doing_extended_query_message = false;
			break;

		case 'c':				/* 复制完成 */
		case 'f':				/* 复制失败 */
			fc_maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
			doing_extended_query_message = false;
			break;

		default:

			/*
			 * 否则我们从前端获得了垃圾。我们将其视为
			 * 致命，因为我们可能已经失去了消息边界同步，
			 * 并且没有好的恢复方法。
			 */
			ereport(FATAL,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("invalid frontend message type %d", fc_qtype)));
			fc_maxmsglen = 0;		/* 保持编译器安静 */
			break;
	}

	/*
	 * 在协议版本3中，所有前端消息都有一个长度词
	 * 紧接在类型代码之后；我们可以独立于
	 * 类型读取消息内容。
	 */
	if (pq_getmessage(fc_inBuf, fc_maxmsglen))
		return EOF;				/* 合适的消息已记录 */
	RESUME_CANCEL_INTERRUPTS();

	return fc_qtype;
}

/* ----------------
 *		ReadCommand从前端或
 *		标准输入读取命令，将其放入inBuf中，并返回
 *		消息类型代码（消息的第一个字节）。
 *		如果到达文件末尾，将返回EOF。
 * ----------------
 */
static int fc_ReadCommand(StringInfo fc_inBuf)
{
	int			fc_result;

	if (whereToSendOutput == DestRemote)
		fc_result = fc_SocketBackend(fc_inBuf);
	else
		fc_result = fc_InteractiveBackend(fc_inBuf);
	return fc_result;
}

/*
 * ProcessClientReadInterrupt() - 处理特定于客户端读取的中断
 *
 * 这在低级读取之前和之后调用。
 * 'blocked' 为 true，如果没有数据可供读取，并且我们计划重试；
 * 如果即将读取或已完成读取，则为 false。
 *
 * 必须保留 errno！
 */
void ProcessClientReadInterrupt(bool fc_blocked)
{
	int			fc_save_errno = errno;

	if (DoingCommandRead)
	{
		/* 检查在读取之前/过程中到达的一般中断 */
		CHECK_FOR_INTERRUPTS();

		/* 处理sinval补充中断（如果有） */
		if (catchupInterruptPending)
			ProcessCatchupInterrupt();

		/* 处理通知中断（如果有） */
		if (notifyInterruptPending)
			ProcessNotifyInterrupt(true);
	}
	else if (ProcDiePending)
	{
		/*
		 * 我们正在死亡。如果没有可读取的数据，则安全
		 * （而且理智）现在处理它。如果我们还没有尝试读取，
		 * 请确保进程门闩已设置，以便如果没有数据
		 * 那么我们将返回这里并死亡。如果我们完成了读取，还要
		 * 确保进程门闩已设置，因为我们在读取时可能不小心
		 * 清除了它。
		 */
		if (fc_blocked)
			CHECK_FOR_INTERRUPTS();
		else
			SetLatch(MyLatch);
	}

	errno = fc_save_errno;
}

/*
 * ProcessClientWriteInterrupt() - 处理特定于客户端写入的中断
 *
 * 这在低级写入之前和之后调用。
 * 'blocked' 为 true，如果没有数据可以写入并且我们计划重试；
 * 如果即将写入或已完成写入，则为 false。
 *
 * 必须保留 errno！
 */
void ProcessClientWriteInterrupt(bool fc_blocked)
{
	int			fc_save_errno = errno;

	if (ProcDiePending)
	{
		/*
		 * 我们正在死去。如果无法写入，那么我们应该立即处理这个问题，否则一个被阻塞的客户端可能会无限期地延迟我们对信号的响应。如果我们还没有尝试写入，请确保进程锁定被设置，以便如果写入会阻塞，那么我们将再次返回这里并终止。如果我们完成了写入，也要确保进程锁定被设置，因为在写入时我们可能会不当地清除它。
		 */
		if (fc_blocked)
		{
			/*
			 * 如果 ProcessInterrupts 不能服务于 ProcDiePending，就不要搞乱 whereToSendOutput。
			 */
			if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
			{
				/*
				 * 我们不想向客户端发送错误消息，因为 a) 这可能会再一次阻塞，b) 这可能会导致协议同步的丢失，因为我们可能已经发送了部分协议消息。
				 */
				if (whereToSendOutput == DestRemote)
					whereToSendOutput = DestNone;

				CHECK_FOR_INTERRUPTS();
			}
		}
		else
			SetLatch(MyLatch);
	}

	errno = fc_save_errno;
}

/*
 * 进行原始解析（仅）。
 *
 * 返回一个 parsetrees 列表（RawStmt 节点），因为给定字符串中可能有多个命令。
 *
 * 注意：对于交互式查询，将此例程与分析和重写阶段分开非常重要。分析和重写不能在中止的事务中进行，因为它们需要访问数据库表。因此，我们依赖原始解析器来确定我们是否见过 COMMIT 或 ABORT 命令；当我们处于中止状态时，其他命令不会进一步处理到原始解析阶段。
 */
List * pg_parse_query(const char *fc_query_string)
{
	List	   *fc_raw_parsetree_list;

	TRACE_POSTGRESQL_QUERY_PARSE_START(query_string);

	if (log_parser_stats)
		ResetUsage();

	fc_raw_parsetree_list = raw_parser(fc_query_string, RAW_PARSE_DEFAULT);

	if (log_parser_stats)
		ShowUsage("PARSER STATISTICS");

#ifdef COPY_PARSE_PLAN_TREES
	/* 可选调试检查：通过 copyObject() 传递原始 parsetrees */
	{
		List	   *fc_new_list = copyObject(fc_raw_parsetree_list);

		/* 这检查了 copyObject() 和 equal() 例程... */
		if (!equal(fc_new_list, fc_raw_parsetree_list))
			elog(WARNING, "copyObject() failed to produce an equal raw parse tree");
		else
			fc_raw_parsetree_list = fc_new_list;
	}
#endif

	/*
	 * 目前，对于许多原始解析树节点，缺少 outfuncs/readfuncs 支持，因此我们不尝试在这里实现 WRITE_READ_PARSE_PLAN_TREES。
	 */

	TRACE_POSTGRESQL_QUERY_PARSE_DONE(query_string);

	return fc_raw_parsetree_list;
}

/*
 * 给定一个原始解析树（gram.y 输出），并可选地提供关于参数符号 ($n) 类型的信息，执行解析分析和规则重写。
 *
 * 返回一个 Query 节点列表，因为分析器或重写器可能将一个查询扩展为多个查询。
 *
 * 注意：出于上述原因，这必须与原始解析分开。
 */
List * pg_analyze_and_rewrite_fixedparams(RawStmt *fc_parsetree,
								   const char *fc_query_string,
								   const Oid *fc_paramTypes,
								   int fc_numParams,
								   QueryEnvironment *fc_queryEnv)
{
	Query	   *fc_query;
	List	   *fc_querytree_list;

	TRACE_POSTGRESQL_QUERY_REWRITE_START(query_string);

	/*
	 * (1) 执行解析分析。
	 */
	if (log_parser_stats)
		ResetUsage();

	fc_query = parse_analyze_fixedparams(fc_parsetree, fc_query_string, fc_paramTypes, fc_numParams,
									  fc_queryEnv);

	if (log_parser_stats)
		ShowUsage("PARSE ANALYSIS STATISTICS");

	/*
	 * (2) 根据需要重写查询
	 */
	fc_querytree_list = pg_rewrite_query(fc_query);

	TRACE_POSTGRESQL_QUERY_REWRITE_DONE(query_string);

	return fc_querytree_list;
}

/*
 * 执行解析分析和重写。这与 pg_analyze_and_rewrite_fixedparams 相同，仅不同于可以从上下文中推导 $n 符号数据类型的信息。
 */
List * pg_analyze_and_rewrite_varparams(RawStmt *fc_parsetree,
								 const char *fc_query_string,
								 Oid **fc_paramTypes,
								 int *fc_numParams,
								 QueryEnvironment *fc_queryEnv)
{
	Query	   *fc_query;
	List	   *fc_querytree_list;

	TRACE_POSTGRESQL_QUERY_REWRITE_START(query_string);

	/*
	 * (1) 执行解析分析。
	 */
	if (log_parser_stats)
		ResetUsage();

	fc_query = parse_analyze_varparams(fc_parsetree, fc_query_string, fc_paramTypes, fc_numParams,
									fc_queryEnv);

	/*
	 * 检查所有参数类型是否已确定。
	 */
	for (int fc_i = 0; fc_i < *fc_numParams; fc_i++)
	{
		Oid			fc_ptype = (*fc_paramTypes)[fc_i];

		if (fc_ptype == InvalidOid || fc_ptype == UNKNOWNOID)
			ereport(ERROR,
					(errcode(ERRCODE_INDETERMINATE_DATATYPE),
					 errmsg("could not determine data type of parameter $%d",
							fc_i + 1)));
	}

	if (log_parser_stats)
		ShowUsage("PARSE ANALYSIS STATISTICS");

	/*
	 * (2) 根据需要重写查询
	 */
	fc_querytree_list = pg_rewrite_query(fc_query);

	TRACE_POSTGRESQL_QUERY_REWRITE_DONE(query_string);

	return fc_querytree_list;
}

/*
 * 执行解析分析和重写。这与 pg_analyze_and_rewrite_fixedparams 相同，只是，不是固定参数数据类型列表，而是提供一个解析器回调，可以进行外部参数解析和可能的其他操作。
 */
List * pg_analyze_and_rewrite_withcb(RawStmt *fc_parsetree,
							  const char *fc_query_string,
							  ParserSetupHook fc_parserSetup,
							  void *fc_parserSetupArg,
							  QueryEnvironment *fc_queryEnv)
{
	Query	   *fc_query;
	List	   *fc_querytree_list;

	TRACE_POSTGRESQL_QUERY_REWRITE_START(query_string);

	/*
	 * (1) 执行解析分析。
	 */
	if (log_parser_stats)
		ResetUsage();

	fc_query = parse_analyze_withcb(fc_parsetree, fc_query_string, fc_parserSetup, fc_parserSetupArg,
								 fc_queryEnv);

	if (log_parser_stats)
		ShowUsage("PARSE ANALYSIS STATISTICS");

	/*
	 * (2) 根据需要重写查询
	 */
	fc_querytree_list = pg_rewrite_query(fc_query);

	TRACE_POSTGRESQL_QUERY_REWRITE_DONE(query_string);

	return fc_querytree_list;
}

/*
 * 执行由解析分析产生的查询的重写。
 *
 * 注意：查询必须刚刚来自解析器，因为我们不对其执行 AcquireRewriteLocks()。
 */
List * pg_rewrite_query(Query *fc_query)
{
	List	   *fc_querytree_list;

	if (Debug_print_parse)
		elog_node_display(LOG, "parse tree", fc_query,
						  Debug_pretty_print);

	if (log_parser_stats)
		ResetUsage();

	if (fc_query->commandType == CMD_UTILITY)
	{
		/* 不要重写实用程序，只需将它们转储到结果列表中 */
		fc_querytree_list = list_make1(fc_query);
	}
	else
	{
		/* 重写常规查询 */
		fc_querytree_list = QueryRewrite(fc_query);
	}

	if (log_parser_stats)
		ShowUsage("REWRITER STATISTICS");

#ifdef COPY_PARSE_PLAN_TREES
	/* 可选调试检查：通过 copyObject() 传递查询树 */
	{
		List	   *fc_new_list;

		fc_new_list = copyObject(fc_querytree_list);
		/* 这检查了 copyObject() 和 equal() 例程... */
		if (!equal(fc_new_list, fc_querytree_list))
			elog(WARNING, "copyObject() failed to produce equal parse tree");
		else
			fc_querytree_list = fc_new_list;
	}
#endif

#ifdef WRITE_READ_PARSE_PLAN_TREES
	/* 可选调试检查：通过 outfuncs/readfuncs 传递查询树 */
	{
		List	   *fc_new_list = NIL;
		ListCell   *fc_lc;

		/*
		 * 我们目前缺少大多数实用语句类型的 outfuncs/readfuncs 支持，因此只尝试写入/读取非实用查询。
		 */
		foreach(fc_lc, fc_querytree_list)
		{
			Query	   *fc_query = lfirst_node(Query, fc_lc);

			if (fc_query->commandType != CMD_UTILITY)
			{
				char	   *fc_str = nodeToString(fc_query);
				Query	   *fc_new_query = stringToNodeWithLocations(fc_str);

				/*
				 * queryId 在存储规则中不会被保存，但我们必须在这里保留它，以避免破坏 pg_stat_statements。
				 */
				fc_new_query->queryId = fc_query->queryId;

				fc_new_list = lappend(fc_new_list, fc_new_query);
				pfree(fc_str);
			}
			else
				fc_new_list = lappend(fc_new_list, fc_query);
		}

		/* 这检查了 outfuncs/readfuncs 和 equal() 例程... */
		if (!equal(fc_new_list, fc_querytree_list))
			elog(WARNING, "outfuncs/readfuncs failed to produce equal parse tree");
		else
			fc_querytree_list = fc_new_list;
	}
#endif

	if (Debug_print_rewritten)
		elog_node_display(LOG, "rewritten parse tree", fc_querytree_list,
						  Debug_pretty_print);

	return fc_querytree_list;
}


/*
 * 为单个已重写查询生成一个计划。
 * 这是对 planner() 的一个薄包装，并接受相同的参数。
 */
PlannedStmt * pg_plan_query(Query *fc_querytree, const char *fc_query_string, int fc_cursorOptions,
			  ParamListInfo fc_boundParams)
{
	PlannedStmt *fc_plan;

	/* 实用命令没有计划。 */
	if (fc_querytree->commandType == CMD_UTILITY)
		return NULL;

	/* 规划器必须在调用用户定义函数的情况下有一个快照。 */
	Assert(ActiveSnapshotSet());

	TRACE_POSTGRESQL_QUERY_PLAN_START();

	if (log_planner_stats)
		ResetUsage();

	/* 调用优化器 */
	fc_plan = planner(fc_querytree, fc_query_string, fc_cursorOptions, fc_boundParams);

	if (log_planner_stats)
		ShowUsage("PLANNER STATISTICS");

#ifdef COPY_PARSE_PLAN_TREES
	/* 可选调试检查：通过 copyObject() 传递计划树 */
	{
		PlannedStmt *fc_new_plan = copyObject(fc_plan);

		/*
		 * equal() 目前没有比较计划节点的例程，因此不要尝试在这里测试相等性。也许有一天修复？
		 */
#ifdef NOT_USED
		/* 这检查了 copyObject() 和 equal() 例程... */
		if (!equal(fc_new_plan, fc_plan))
			elog(WARNING, "copyObject() failed to produce an equal plan tree");
		else
#endif
			fc_plan = fc_new_plan;
	}
#endif

#ifdef WRITE_READ_PARSE_PLAN_TREES
	/* 可选调试检查：通过 outfuncs/readfuncs 传递计划树 */
	{
		char	   *fc_str;
		PlannedStmt *fc_new_plan;

		fc_str = nodeToString(fc_plan);
		fc_new_plan = stringToNodeWithLocations(fc_str);
		pfree(fc_str);

		/*
		 * equal() 目前没有比较计划节点的例程，因此不要尝试在这里测试相等性。也许有一天修复？
		 */
#ifdef NOT_USED
		/* 这检查了 outfuncs/readfuncs 和 equal() 例程... */
		if (!equal(fc_new_plan, fc_plan))
			elog(WARNING, "outfuncs/readfuncs failed to produce an equal plan tree");
		else
#endif
			fc_plan = fc_new_plan;
	}
#endif

	/*
	 * 如果在调试，则打印计划。
	 */
	if (Debug_print_plan)
		elog_node_display(LOG, "plan", fc_plan, Debug_pretty_print);

	TRACE_POSTGRESQL_QUERY_PLAN_DONE();

	return fc_plan;
}

/*
 * 为已重写查询的列表生成计划。
 *
 * 对于普通可优化语句，调用计划程序。对于实用程序
 * 语句，仅制作一个包装的PlannedStmt节点。
 *
 * 结果是一个PlannedStmt节点的列表。
 */
List * pg_plan_queries(List *fc_querytrees, const char *fc_query_string, int fc_cursorOptions,
				ParamListInfo fc_boundParams)
{
	List	   *fc_stmt_list = NIL;
	ListCell   *fc_query_list;

	foreach(fc_query_list, fc_querytrees)
	{
		Query	   *fc_query = lfirst_node(Query, fc_query_list);
		PlannedStmt *fc_stmt;

		if (fc_query->commandType == CMD_UTILITY)
		{
			/* 实用程序命令不需要规划。 */
			fc_stmt = makeNode(PlannedStmt);
			fc_stmt->commandType = CMD_UTILITY;
			fc_stmt->canSetTag = fc_query->canSetTag;
			fc_stmt->utilityStmt = fc_query->utilityStmt;
			fc_stmt->stmt_location = fc_query->stmt_location;
			fc_stmt->stmt_len = fc_query->stmt_len;
			fc_stmt->queryId = fc_query->queryId;
		}
		else
		{
			fc_stmt = pg_plan_query(fc_query, fc_query_string, fc_cursorOptions,
								 fc_boundParams);
		}

		fc_stmt_list = lappend(fc_stmt_list, fc_stmt);
	}

	return fc_stmt_list;
}



#ifdef FDD
/*
* 更新配置项fdb.password_rule
*/
static void FDB_Update_PasswordRule_Conf(void)
{
	char *fc_conf_str = GetConfigOptionByName(FDB_PASSWORD_RULE_CONF, NULL, true);
	fdb_authPasswordRule = -1;
	if (fc_conf_str != NULL)
	{
		fdb_authPasswordRule = atoi(fc_conf_str);
	}
}

/*
* 掩盖密码
*/
static const char *fdb_mask_password(const char *fc_sql)
{
	char *fc_commandStr = NULL;
	char *fc_passwordToken = NULL;
	char *fc_nullToken = NULL;
	int fc_i = 0, fc_index = 0;


	fc_commandStr = pstrdup(fc_sql); 
	//全部转换成小写
	for (fc_i = 0; fc_commandStr[fc_i]!= '\0'; fc_i++)
	{
		fc_commandStr[fc_i] = (char)pg_tolower((unsigned char)fc_commandStr[fc_i]);
	}

	//找到password关键字，将密码掩盖
	fc_passwordToken = strstr(fc_commandStr, TOKEN_PASSWORD);
	if (fc_passwordToken != NULL)
	{
		//判断NULL关键字
		fc_nullToken = strstr(fc_commandStr, TOKEN_NULL);
		if (fc_nullToken != NULL)
		{
			fc_index = fc_nullToken - fc_commandStr;
			for( fc_i=0; fc_i<fc_index; fc_i++ ) 
			{ 
				fc_commandStr[fc_i] = fc_sql[fc_i];  
			}
			fc_i = fc_index + strlen(TOKEN_NULL) - 1;
			fc_commandStr[fc_index++] = '*';
		}
		//判断单引号
		else if (strstr(fc_passwordToken, "'") != NULL)
		{
			fc_index = (fc_passwordToken - fc_commandStr) + strlen(TOKEN_PASSWORD);
			//找到第一个单引号
			for (; fc_sql[fc_index] != '\''; fc_index++) 
			{
				
			}
			for(fc_i=0; fc_i<fc_index; fc_i++) 
			{ 
				fc_commandStr[fc_i] = fc_sql[fc_i];  
			}
			fc_commandStr[fc_index++] = '*';
			//跳过到另一个单引号
			for (fc_i = fc_index; fc_sql[fc_i] != '\0' && fc_sql[fc_i] != '\''; fc_i++) 
			{

			}
		}
		else 
		{
			//没有单引号和null关键字，直接返回
			return fc_sql;
		}
		if (fc_sql[fc_i] != '\0')
		{
			//把密码后面那段接上
			for (fc_i++; fc_sql[fc_i] != '\0'; fc_i++)
			{
				fc_commandStr[fc_index++] = fc_sql[fc_i];
			}
		}
		fc_commandStr[fc_index] = '\0';
		return fc_commandStr;
	}
	return fc_sql;
}
#endif //FDD

/*
 * exec_simple_query
 *
 * 执行“简单查询”协议消息。
 */
static void fc_exec_simple_query(const char *fc_query_string)
{
	CommandDest fc_dest = whereToSendOutput;
	MemoryContext fc_oldcontext;
	List	   *fc_parsetree_list;
	ListCell   *fc_parsetree_item;
	bool		fc_save_log_statement_stats = log_statement_stats;
	bool		fc_was_logged = false;
	bool		fc_use_implicit_block;
	char		fc_msec_str[32];

	/*
	 * 向各种监控设施报告查询。
	 */
	debug_query_string = fc_query_string;

	pgstat_report_activity(STATE_RUNNING, fc_query_string);

	TRACE_POSTGRESQL_QUERY_START(query_string);

	/*
	 * 我们使用 save_log_statement_stats，以便 ShowUsage 不会报告不正确的
	 * 结果，因为没有调用 ResetUsage。
	 */
	if (fc_save_log_statement_stats)
		ResetUsage();

	/*
	 * 启动一个事务命令。由 query_string 生成的所有查询都将在同一个命令块中，
	 * 除非我们发现 BEGIN/COMMIT/ABORT 语句；在这些语句之后，我们必须强制一个新的 xact 命令，
	 * 否则在 xact.c 中会发生不好的事情。（注意，这通常会改变当前的内存上下文。）
	 */
	fc_start_xact_command();

	/*
	 * 删除任何先前存在的未命名语句。（虽然这并非严格必要，
	 * 但将简单查询模式定义为使用未命名语句和门户似乎是最好的；
	 * 这确保我们回收任何先前未命名操作使用的存储。）
	 */
	fc_drop_unnamed_stmt();

	/*
	 * 切换到适当的上下文以构造解析树。
	 */
	fc_oldcontext = MemoryContextSwitchTo(MessageContext);

	/*
	 * 对查询或查询进行基本解析（即使我们处于中止事务状态，这也应该是安全的！）
	 */
	fc_parsetree_list = pg_parse_query(fc_query_string);

#ifdef FDD	
	//开启密码校验时，会隐藏密码
	if(fdb_authPasswordRule != PASSWORD_RULE_NONE)
	{
		foreach(fc_parsetree_item, fc_parsetree_list)
		{
			RawStmt    *fc_parsetree = lfirst_node(RawStmt, fc_parsetree_item);
			switch(nodeTag(fc_parsetree->stmt) )
			{
				case T_AlterRoleStmt:
				case T_CreateRoleStmt:
					debug_query_string = fdb_mask_password(debug_query_string);
					break;
				default:
					break;
			}
		}
	}
	
#endif


	/* 如果由 log_statement 规定，则立即记录 */
	if (fc_check_log_statement(fc_parsetree_list))
	{
#ifdef FDD
		ereport(LOG,
				(errmsg("statement: %s", debug_query_string),
				 errhidestmt(true),
				 fc_errdetail_execute(fc_parsetree_list)));
#else
		ereport(LOG,
				(errmsg("statement: %s", fc_query_string),
				 errhidestmt(true),
				 fc_errdetail_execute(fc_parsetree_list)));
#endif //FDD

		fc_was_logged = true;
	}

	/*
	 * 切换回事务上下文以进入循环。
	 */
	MemoryContextSwitchTo(fc_oldcontext);

	/*
	 * 出于历史原因，如果在单个“简单查询”消息中给出多个 SQL 语句，
	 * 除非包含显式事务控制命令以使列表的部分为单独的事务，
	 * 否则我们将它们作为一个单一事务执行。为了在事务机制中正确地表示这种行为，
	 * 我们使用“隐式”事务块。
	 */
	fc_use_implicit_block = (list_length(fc_parsetree_list) > 1);

	/*
	 * 遍历原始解析树并处理每一个。
	 */
	foreach(fc_parsetree_item, fc_parsetree_list)
	{
		RawStmt    *fc_parsetree = lfirst_node(RawStmt, fc_parsetree_item);
		bool		fc_snapshot_set = false;
		CommandTag	fc_commandTag;
		QueryCompletion fc_qc;
		MemoryContext fc_per_parsetree_context = NULL;
		List	   *fc_querytree_list,
				   *fc_plantree_list;
		Portal		fc_portal;
		DestReceiver *fc_receiver;
		int16		fc_format;

		pgstat_report_query_id(0, true);

		/*
		 * 获取用于状态显示的命令名称（它也成为默认的完成标签，在 PortalRun 内部）。
		 * 设置 ps_status，并进行任何目标所需的特殊 SQL 命令开始处理。
		 */
		fc_commandTag = CreateCommandTag(fc_parsetree->stmt);

		set_ps_display(GetCommandTagName(fc_commandTag));

		BeginCommand(fc_commandTag, fc_dest);

#ifdef FDD
		if(NULL != fdb_generate_audit_event_hook)
		{ 
			fdb_generate_audit_event_hook(fc_parsetree->stmt, fc_query_string);//先预保存SQL，避免还没有执行ExecutorStart_hook就报错了
		}
#endif //FDD
		/*
		 * 如果我们处于中止事务中，拒绝所有命令，除了 COMMIT/ABORT。
		 * 在尝试进行解析分析、重写或规划之前进行此检查很重要，
		 * 因为所有这些阶段都尝试进行数据库访问，可能会在中止状态下失败。
		 * （在这种状态下，可能安全地允许一些额外的实用命令，但数量不多……）
		 */
		if (IsAbortedTransactionBlockState() &&
			!fc_IsTransactionExitStmt(fc_parsetree->stmt))
			ereport(ERROR,
					(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
					 errmsg("current transaction is aborted, "
							"commands ignored until end of transaction block"),
					 fc_errdetail_abort()));

		/* 确保我们处于事务命令中 */
		fc_start_xact_command();

		/*
		 * 如果使用隐式事务块，并且我们尚未处于事务块中，
		 * 启动隐式块以强制将此语句与任何后续语句分组在一起。
		 * （我们必须在每次通过循环时执行此操作；否则，列表中的 COMMIT/ROLLBACK
		 * 会导致后续语句不被分组。）
		 */
		if (fc_use_implicit_block)
			BeginImplicitTransactionBlock();

		/* 如果在解析或之前的命令中收到取消信号，则退出 */
		CHECK_FOR_INTERRUPTS();

		/*
		 * 如果解析分析/规划需要快照，则设置一个快照。
		 */
		if (analyze_requires_snapshot(fc_parsetree))
		{
			PushActiveSnapshot(GetTransactionSnapshot());
			fc_snapshot_set = true;
		}

		/*
		 * 可以分析、重写和规划此查询。
		 *
		 * 切换到适当的上下文以构造查询和计划树
		 * （这些不能在事务上下文中，因为当命令被 COMMIT/ROLLBACK 时会被重置）。
		 * 如果我们有多个解析树，则为每一个使用单独的上下文，
		 * 以便我们可以在移动到下一个时释放该内存。
		 * 但对于最后一个（或唯一的）解析树，只需使用 MessageContext，
		 * 它将在完成后不久被重置。在出错情况下，per_parsetree_context
		 * 将在 MessageContext 被重置时被删除。
		 */
		if (lnext(fc_parsetree_list, fc_parsetree_item) != NULL)
		{
			fc_per_parsetree_context =
				AllocSetContextCreate(MessageContext,
									  "per-parsetree message context",
									  ALLOCSET_DEFAULT_SIZES);
			fc_oldcontext = MemoryContextSwitchTo(fc_per_parsetree_context);
		}
		else
			fc_oldcontext = MemoryContextSwitchTo(MessageContext);

		fc_querytree_list = pg_analyze_and_rewrite_fixedparams(fc_parsetree, fc_query_string,
															NULL, 0, NULL);

		fc_plantree_list = pg_plan_queries(fc_querytree_list, fc_query_string,
										CURSOR_OPT_PARALLEL_OK, NULL);

		/*
		 * 完成用于解析/规划的快照。
		 *
		 * 尽管重用相同的快照用于查询执行看起来很有前景（至少对于简单协议），
		 * 不幸的是，它会导致执行使用在锁定查询中提到的任何表之前
		 * 获取的快照。这会造成用户可见的异常，因此请避免。详细信息请参见
		 * https://postgr.es/m/flat/5075D8DF.6050500@fuzzy.cz。
		 */
		if (fc_snapshot_set)
			PopActiveSnapshot();

		/* 如果在分析或规划中收到取消信号，则退出 */
		CHECK_FOR_INTERRUPTS();

		/*
		 * 创建未命名的门户以运行查询或查询。如果已经存在一个，则静默丢弃。
		 */
		fc_portal = CreatePortal("", true, true);
		/* 不在 pg_cursors 中显示门户 */
		fc_portal->visible = false;

		/*
		 * 我们不必将任何内容复制到门户中，
		 * 因为我们在这里传递的所有内容都在 MessageContext 或 per_parsetree_context 中，
		 * 并且无论如何都会比门户活得更久。
		 */
		PortalDefineQuery(fc_portal,
						  NULL,
						  fc_query_string,
						  fc_commandTag,
						  fc_plantree_list,
						  NULL);

		/*
		 * 启动门户。这里没有参数。
		 */
		PortalStart(fc_portal, NULL, 0, InvalidSnapshot);

		/*
		 * 选择适当的输出格式：文本，除非我们正在从二进制光标中 FETCH。
		 * （在这里这样做真的很麻烦——但它避免了其他地方的麻烦。
		 * 啊，向后兼容的乐趣……）
		 */
		fc_format = 0;				/* TEXT 是默认 */
		if (IsA(fc_parsetree->stmt, FetchStmt))
		{
			FetchStmt  *fc_stmt = (FetchStmt *) fc_parsetree->stmt;

			if (!fc_stmt->ismove)
			{
				Portal		fc_fportal = GetPortalByName(fc_stmt->portalname);

				if (PortalIsValid(fc_fportal) &&
					(fc_fportal->cursorOptions & CURSOR_OPT_BINARY))
					fc_format = 1; /* BINARY */
			}
		}
		PortalSetResultFormat(fc_portal, 1, &fc_format);

		/*
		 * 现在我们可以创建目标接收器对象。
		 */
		fc_receiver = CreateDestReceiver(fc_dest);
		if (fc_dest == DestRemote)
			SetRemoteDestReceiverParams(fc_receiver, fc_portal);

		
/*
		 * 切换回事务上下文以执行。
		 */
		MemoryContextSwitchTo(fc_oldcontext);

		/*
		 * 运行门户直到完成，然后将其删除（及接收器）。
		 */
		(void) PortalRun(fc_portal,
						 FETCH_ALL,
						 true,	/* 始终是顶层 */
						 true,	/* ignored */
						 fc_receiver,
						 fc_receiver,
						 &fc_qc);

		fc_receiver->rDestroy(fc_receiver);

		PortalDrop(fc_portal, false);

		if (lnext(fc_parsetree_list, fc_parsetree_item) == NULL)
		{
			/*
			 * 如果这是查询字符串的最后一个解析树，在报告命令完成之前关闭事务语句。这样可以在发出命令完成消息之前报告任何事务结束错误，以避免使客户困惑，他们期望收到命令完成消息或错误，而不是先后收到一个和另一个。此外，如果我们正在使用隐式事务块，我们必须首先关闭它。
			 */
			if (fc_use_implicit_block)
				EndImplicitTransactionBlock();
			fc_finish_xact_command();
		}
		else if (IsA(fc_parsetree->stmt, TransactionStmt))
		{
			/*
			 * 如果这是一个事务控制语句，则提交它。我们将在下一个命令中开始一个新的 xact 命令。
			 */
			fc_finish_xact_command();
		}
		else
		{
			/*
			 * 如果我们没有调用 finish_xact_command()，那么最好不要看到设置了 XACT_FLAGS_NEEDIMMEDIATECOMMIT。(隐式事务块应该已经防止它被设置。)
			 */
			Assert(!(MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT));

			/*
			 * 每个查询后我们都需要调用 CommandCounterIncrement，除了启动或结束事务块的查询。
			 */
			CommandCounterIncrement();

			/*
			 * 在多查询字符串的查询之间禁用语句超时，以便每个查询的超时分别生效。（我们的下一次循环迭代将启动一个新的超时。）
			 */
			fc_disable_statement_timeout();
		}

		/*
		 * 告诉客户端我们已经完成此查询。请注意，我们为每个原始解析树发出恰好一个 EndCommand 报告，因此对于客户端发送的每个 SQL 命令，无论重写如何都是如此。（但因错误而中止的命令将根本不会发送 EndCommand 报告。）
		 */
		EndCommand(&fc_qc, fc_dest, false);

		/* 现在我们可以删除每个解析树上下文，如果它被创建的话。 */
		if (fc_per_parsetree_context)
			MemoryContextDelete(fc_per_parsetree_context);
	}							/* 结束解析树的循环 */

	/*
	 * 关闭事务语句，如果有一个是开放的。（只有当解析树列表为空时，这才会有作用；否则，最后一次循环迭代已经完成了它。）
	 */
	fc_finish_xact_command();

	/*
	 * 如果没有解析树，则返回 EmptyQueryResponse 消息。
	 */
	if (!fc_parsetree_list)
		NullCommand(fc_dest);

	/*
	 * 如果合适则记录持续时间。
	 */
	switch (check_log_duration(fc_msec_str, fc_was_logged))
	{
		case 1:
			ereport(LOG,
					(errmsg("duration: %s ms", fc_msec_str),
					 errhidestmt(true)));
			break;
		case 2:
			ereport(LOG,
					(errmsg("duration: %s ms  statement: %s",
							fc_msec_str, fc_query_string),
					 errhidestmt(true),
					 fc_errdetail_execute(fc_parsetree_list)));
			break;
	}

	if (fc_save_log_statement_stats)
		ShowUsage("QUERY STATISTICS");

	TRACE_POSTGRESQL_QUERY_DONE(query_string);

	debug_query_string = NULL;
}

/*
 * exec_parse_message
 *
 * 执行 "Parse" 协议消息。
 */
static void fc_exec_parse_message(const char *fc_query_string,	/* 要执行的字符串 */
				   const char *fc_stmt_name,	/* 准备的 stmt 名称 */
				   Oid *fc_paramTypes, /* 参数类型 */
				   int fc_numParams)	/* 参数数量 */
{
	MemoryContext fc_unnamed_stmt_context = NULL;
	MemoryContext fc_oldcontext;
	List	   *fc_parsetree_list;
	RawStmt    *fc_raw_parse_tree;
	List	   *fc_querytree_list;
	CachedPlanSource *fc_psrc;
	bool		fc_is_named;
	bool		fc_save_log_statement_stats = log_statement_stats;
	char		fc_msec_str[32];

	/*
	 * 向各种监控设施报告查询。
	 */
	debug_query_string = fc_query_string;

	pgstat_report_activity(STATE_RUNNING, fc_query_string);

	set_ps_display("PARSE");

	if (fc_save_log_statement_stats)
		ResetUsage();

	ereport(DEBUG2,
			(errmsg_internal("parse %s: %s",
							 *fc_stmt_name ? fc_stmt_name : "<unnamed>",
							 fc_query_string)));

	/*
	 * 启动一个事务命令，以便我们可以运行解析分析等。（注意，这通常会改变当前内存上下文。）如果我们已经在一个事务中，则没有任何动作。这也会在必要时启动语句超时。
	 */
	fc_start_xact_command();

	/*
	 * 切换到适当的上下文以构造解析树。
	 *
	 * 我们根据准备语句是否命名有两种策略。对于命名的准备语句，我们在 MessageContext 中进行解析，并将完成的树复制到准备语句的规划缓存条目中；然后 MessageContext 的重置会释放解析和重写所使用的临时空间。对于未命名的准备语句，我们假设语句不会长时间存在，因此快速摆脱临时空间可能不值得复制解析树的成本。因此，在这种情况下，我们在这里创建计划缓存条目的查询上下文，并在其中完成所有解析工作。
	 */
	fc_is_named = (fc_stmt_name[0] != '\0');
	if (fc_is_named)
	{
		/* 命名的准备语句 --- 在 MessageContext 中解析 */
		fc_oldcontext = MemoryContextSwitchTo(MessageContext);
	}
	else
	{
		/* 未命名的准备语句 --- 释放任何先前的未命名 stmt */
		fc_drop_unnamed_stmt();
		/* 创建解析上下文 */
		fc_unnamed_stmt_context =
			AllocSetContextCreate(MessageContext,
								  "unnamed prepared statement",
								  ALLOCSET_DEFAULT_SIZES);
		fc_oldcontext = MemoryContextSwitchTo(fc_unnamed_stmt_context);
	}

	/*
	 * 对查询或查询进行基本解析（即使我们处于中止事务状态，这也应该是安全的！）
	 */
	fc_parsetree_list = pg_parse_query(fc_query_string);

	/*
	 * 我们仅允许在准备语句中包含单个用户语句。这主要是为了保持协议简单 --- 否则我们需要担心多个结果 tupdesc 之类的事情。
	 */
	if (list_length(fc_parsetree_list) > 1)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("cannot insert multiple commands into a prepared statement")));

	if (fc_parsetree_list != NIL)
	{
		bool		fc_snapshot_set = false;

		fc_raw_parse_tree = linitial_node(RawStmt, fc_parsetree_list);

		/*
		 * 如果我们在一个中止的事务中，拒绝所有命令，除了 COMMIT/ROLLBACK。重要的是在我们尝试进行解析分析、重写或规划之前进行此测试，因为所有这些阶段都试图进行数据库访问，而这些访问在中止状态下可能会失败。（在这种状态下，可能安全地允许一些额外的实用命令，但不多……）
		 */
		if (IsAbortedTransactionBlockState() &&
			!fc_IsTransactionExitStmt(fc_raw_parse_tree->stmt))
			ereport(ERROR,
					(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
					 errmsg("current transaction is aborted, "
							"commands ignored until end of transaction block"),
					 fc_errdetail_abort()));

		/*
		 * 在我们进行解析分析之前创建 CachedPlanSource，因为它需要看到未修改的原始解析树。
		 */
		fc_psrc = CreateCachedPlan(fc_raw_parse_tree, fc_query_string,
								CreateCommandTag(fc_raw_parse_tree->stmt));

		/*
		 * 如果解析分析需要快照，则设置一个快照。
		 */
		if (analyze_requires_snapshot(fc_raw_parse_tree))
		{
			PushActiveSnapshot(GetTransactionSnapshot());
			fc_snapshot_set = true;
		}

		/*
		 * 分析和重写查询。注意，最初指定的参数集不要求是完整的，因此我们必须使用 pg_analyze_and_rewrite_varparams()。
		 */
		fc_querytree_list = pg_analyze_and_rewrite_varparams(fc_raw_parse_tree,
														  fc_query_string,
														  &fc_paramTypes,
														  &fc_numParams,
														  NULL);

		/* 完成用于解析的快照 */
		if (fc_snapshot_set)
			PopActiveSnapshot();
	}
	else
	{
		/* 空输入字符串。这是合法的。 */
		fc_raw_parse_tree = NULL;
		fc_psrc = CreateCachedPlan(fc_raw_parse_tree, fc_query_string,
								CMDTAG_UNKNOWN);
		fc_querytree_list = NIL;
	}

	/*
	 * CachedPlanSource 必须是 MessageContext 的直接子节点，然后我们才能将 unnamed_stmt_context 重新归类到它之下，否则我们将会有一个不连接的循环子图。虽然有点复杂，但比更上面切换上下文要好。
	 */
	if (fc_unnamed_stmt_context)
		MemoryContextSetParent(fc_psrc->context, MessageContext);

	/* 完成填充CachedPlanSource */
	CompleteCachedPlan(fc_psrc,
					   fc_querytree_list,
					   fc_unnamed_stmt_context,
					   fc_paramTypes,
					   fc_numParams,
					   NULL,
					   NULL,
					   CURSOR_OPT_PARALLEL_OK,	/* 允许并行模式 */
					   true);	/* 固定结果 */

	/* 如果在分析期间收到取消信号，则退出 */
	CHECK_FOR_INTERRUPTS();

	if (fc_is_named)
	{
		/*
		 * 将查询存储为准备语句。
		 */
		StorePreparedStatement(fc_stmt_name, fc_psrc, false);
	}
	else
	{
		/*
		 * 我们只需将 CachedPlanSource 保存到 unnamed_stmt_psrc 中。
		 */
		SaveCachedPlan(fc_psrc);
		unnamed_stmt_psrc = fc_psrc;
	}

	MemoryContextSwitchTo(fc_oldcontext);

	/*
	 * 我们在这里不关闭打开的事务命令；只有当客户端发送 Sync 时才会发生这种情况。
	 * 相反，进行 CommandCounterIncrement，以防在解析/计划期间发生了什么。
	 */
	CommandCounterIncrement();

	/*
	 * 发送 ParseComplete。
	 */
	if (whereToSendOutput == DestRemote)
		pq_putemptymessage('1');

	/*
	 * 如果合适则记录持续时间。
	 */
	switch (check_log_duration(fc_msec_str, false))
	{
		case 1:
			ereport(LOG,
					(errmsg("duration: %s ms", fc_msec_str),
					 errhidestmt(true)));
			break;
		case 2:
			ereport(LOG,
					(errmsg("duration: %s ms  parse %s: %s",
							fc_msec_str,
							*fc_stmt_name ? fc_stmt_name : "<unnamed>",
							fc_query_string),
					 errhidestmt(true)));
			break;
	}

	if (fc_save_log_statement_stats)
		ShowUsage("PARSE MESSAGE STATISTICS");

	debug_query_string = NULL;
}

/*
 * exec_bind_message
 *
 * 处理 "Bind" 消息以从准备好的语句创建一个 portal
 */
static void fc_exec_bind_message(StringInfo fc_input_message)
{
	const char *fc_portal_name;
	const char *fc_stmt_name;
	int			fc_numPFormats;
	int16	   *fc_pformats = NULL;
	int			fc_numParams;
	int			fc_numRFormats;
	int16	   *fc_rformats = NULL;
	CachedPlanSource *fc_psrc;
	CachedPlan *fc_cplan;
	Portal		fc_portal;
	char	   *fc_query_string;
	char	   *fc_saved_stmt_name;
	ParamListInfo fc_params;
	MemoryContext fc_oldContext;
	bool		fc_save_log_statement_stats = log_statement_stats;
	bool		fc_snapshot_set = false;
	char		fc_msec_str[32];
	ParamsErrorCbData fc_params_data;
	ErrorContextCallback fc_params_errcxt;
	ListCell   *fc_lc;

	/* 获取消息的固定部分 */
	fc_portal_name = pq_getmsgstring(fc_input_message);
	fc_stmt_name = pq_getmsgstring(fc_input_message);

	ereport(DEBUG2,
			(errmsg_internal("bind %s to %s",
							 *fc_portal_name ? fc_portal_name : "<unnamed>",
							 *fc_stmt_name ? fc_stmt_name : "<unnamed>")));

	/* 查找准备好的语句 */
	if (fc_stmt_name[0] != '\0')
	{
		PreparedStatement *fc_pstmt;

		fc_pstmt = FetchPreparedStatement(fc_stmt_name, true);
		fc_psrc = fc_pstmt->plansource;
	}
	else
	{
		/* 特殊情况处理未命名语句 */
		fc_psrc = unnamed_stmt_psrc;
		if (!fc_psrc)
#ifdef FDD //cppcheck
		{
#endif
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
					 errmsg("unnamed prepared statement does not exist")));
#ifdef FDD //cppcheck
			return;
		}
#endif
	}

	/*
	 * 向各种监控设施报告查询。
	 */
	debug_query_string = fc_psrc->query_string;

	pgstat_report_activity(STATE_RUNNING, fc_psrc->query_string);

	foreach(fc_lc, fc_psrc->query_list)
	{
		Query	   *fc_query = lfirst_node(Query, fc_lc);

		if (fc_query->queryId != UINT64CONST(0))
		{
			pgstat_report_query_id(fc_query->queryId, false);
			break;
		}
	}

	set_ps_display("BIND");

	if (fc_save_log_statement_stats)
		ResetUsage();

	/*
	 * 启动一个事务命令，以便我们可以调用函数等。（注意，这
	 * 通常会改变当前内存上下文。）如果我们已经在一个事务中，则不会发生任何事情。
	 * 这也会在必要时激活语句超时。
	 */
	fc_start_xact_command();

	/* 切换回消息上下文 */
	MemoryContextSwitchTo(MessageContext);

	/* 获取参数格式代码 */
	fc_numPFormats = pq_getmsgint(fc_input_message, 2);
	if (fc_numPFormats > 0)
	{
		fc_pformats = (int16 *) palloc(fc_numPFormats * sizeof(int16));
		for (int fc_i = 0; fc_i < fc_numPFormats; fc_i++)
			fc_pformats[fc_i] = pq_getmsgint(fc_input_message, 2);
	}

	/* 获取参数值计数 */
	fc_numParams = pq_getmsgint(fc_input_message, 2);

	if (fc_numPFormats > 1 && fc_numPFormats != fc_numParams)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("bind message has %d parameter formats but %d parameters",
						fc_numPFormats, fc_numParams)));

	if (fc_numParams != fc_psrc->num_params)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("bind message supplies %d parameters, but prepared statement \"%s\" requires %d",
						fc_numParams, fc_stmt_name, fc_psrc->num_params)));

	/*
	 * 如果我们处于中止事务状态，唯一可以实际运行的 portal
	 * 是包含 COMMIT 或 ROLLBACK 命令的 portal。我们
	 * 不允许绑定其他任何内容，以避免与期望在有效事务内部运行的基础设施出现问题。
	 * 我们还不允许绑定任何参数，因为我们不能冒险调用用户定义的 I/O
	 * 函数。
	 */
	if (IsAbortedTransactionBlockState() &&
		(!(fc_psrc->raw_parse_tree &&
		   fc_IsTransactionExitStmt(fc_psrc->raw_parse_tree->stmt)) ||
		 fc_numParams != 0))
		ereport(ERROR,
				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
				 errmsg("current transaction is aborted, "
						"commands ignored until end of transaction block"),
				 fc_errdetail_abort()));

	/*
	 * 创建 portal。仅在指定未命名的 portal 时允许无声替换现有 portal。
	 */
	if (fc_portal_name[0] == '\0')
		fc_portal = CreatePortal(fc_portal_name, true, true);
	else
		fc_portal = CreatePortal(fc_portal_name, false, false);

	/*
	 * 准备将内容复制到 portal 的内存上下文中。我们首先进行所有这些
	 * 复制，因为它可能会失败（内存不足），我们不想让 GetCachedPlan 和
	 * PortalDefineQuery 之间发生故障；那将导致泄漏我们的 plancache 引用计数。
	 */
	fc_oldContext = MemoryContextSwitchTo(fc_portal->portalContext);

	/* 将计划的查询字符串复制到门户中 */
	fc_query_string = pstrdup(fc_psrc->query_string);

	/* 如果未命名，则同样复制语句名称 */
	if (fc_stmt_name[0])
		fc_saved_stmt_name = pstrdup(fc_stmt_name);
	else
		fc_saved_stmt_name = NULL;

	/*
	 * 如果我们有参数需要获取（因为输入
	 * 函数可能需要它）或者查询不是实用程序命令（因此可能需要重新执行解析分析和规划），
	 * 则设置快照。我们保持快照处于活动状态直到完成，以便 plancache.c 不必
	 * 再次获取新的快照。
	 */
	if (fc_numParams > 0 ||
		(fc_psrc->raw_parse_tree &&
		 analyze_requires_snapshot(fc_psrc->raw_parse_tree)))
	{
		PushActiveSnapshot(GetTransactionSnapshot());
		fc_snapshot_set = true;
	}

	/*
	 * 获取参数（如果有），并存储在 portal 的内存上下文中。
	 */
	if (fc_numParams > 0)
	{
		char	  **fc_knownTextValues = NULL; /* 在首次使用时分配 */
		BindParamCbData fc_one_param_data;

		/*
		 * 设置错误回调，以便如果在此阶段出现错误，
		 * 我们可以报告导致问题的特定参数。
		 */
		fc_one_param_data.portalName = fc_portal->name;
		fc_one_param_data.paramno = -1;
		fc_one_param_data.paramval = NULL;
		fc_params_errcxt.previous = error_context_stack;
		fc_params_errcxt.callback = fc_bind_param_error_callback;
		fc_params_errcxt.arg = (void *) &fc_one_param_data;
		error_context_stack = &fc_params_errcxt;

		fc_params = makeParamList(fc_numParams);

		for (int fc_paramno = 0; fc_paramno < fc_numParams; fc_paramno++)
		{
			Oid			fc_ptype = fc_psrc->param_types[fc_paramno];
			int32		fc_plength;
			Datum		fc_pval;
			bool		fc_isNull;
			StringInfoData fc_pbuf;
			char		fc_csave;
			int16		fc_pformat;

			fc_one_param_data.paramno = fc_paramno;
			fc_one_param_data.paramval = NULL;

			fc_plength = pq_getmsgint(fc_input_message, 4);
			fc_isNull = (fc_plength == -1);

			if (!fc_isNull)
			{
				const char *fc_pvalue = pq_getmsgbytes(fc_input_message, fc_plength);

				/*
				 * 我们只是设置一个虚假的
				 * StringInfo，指向消息缓冲区的正确部分。
				 * 我们假设可以在消息缓冲区上写入，以
				 * 维持 StringInfos 具有
				 * 尾随空值的约定。这是麻烦的，但在处理非常大的参数字符串时是一个巨大的胜利。
				 */
				fc_pbuf.data = unconstify(char *, fc_pvalue);
				fc_pbuf.maxlen = fc_plength + 1;
				fc_pbuf.len = fc_plength;
				fc_pbuf.cursor = 0;

				fc_csave = fc_pbuf.data[fc_plength];
				fc_pbuf.data[fc_plength] = '\0';
			}
			else
			{
				fc_pbuf.data = NULL;	/* 保持编译器安静 */
				fc_csave = 0;
			}

			if (fc_numPFormats > 1)
				fc_pformat = fc_pformats[fc_paramno];
			else if (fc_numPFormats > 0)
				fc_pformat = fc_pformats[0];
			else
				fc_pformat = 0;	/* 默认 = 文本 */

			if (fc_pformat == 0)	/* 文本模式 */
			{
				Oid			fc_typinput;
				Oid			fc_typioparam;
				char	   *fc_pstring;

				getTypeInputInfo(fc_ptype, &fc_typinput, &fc_typioparam);

				/*
				 * 在调用
				 * typinput 例程之前，我们必须进行编码转换。
				 */
				if (fc_isNull)
					fc_pstring = NULL;
				else
					fc_pstring = pg_client_to_server(fc_pbuf.data, fc_plength);

				/* 现在我们可以在错误情况下记录输入字符串 */
				fc_one_param_data.paramval = fc_pstring;

				fc_pval = OidInputFunctionCall(fc_typinput, fc_pstring, fc_typioparam, -1);

				fc_one_param_data.paramval = NULL;

				/*
				 * 如果以后可能需要记录参数，请在 MessageContext 中保存
				 * 转换字符串的副本；然后释放编码转换的结果（如果有）。
				 */
				if (fc_pstring)
				{
					if (log_parameter_max_length_on_error != 0)
					{
						MemoryContext fc_oldcxt;

						fc_oldcxt = MemoryContextSwitchTo(MessageContext);

						if (fc_knownTextValues == NULL)
							fc_knownTextValues =
								palloc0(fc_numParams * sizeof(char *));

						if (log_parameter_max_length_on_error < 0)
							fc_knownTextValues[fc_paramno] = pstrdup(fc_pstring);
						else
						{
							/*
							 * 我们可以修剪保存的字符串，知道我们
							 * 不会打印全部内容。但我们必须至少复制
							 * 两个完整字符，以便 BuildParamLogString 能够使用；
							 * 否则它可能会无法包含尾随省略号。
							 */
							fc_knownTextValues[fc_paramno] =
								pnstrdup(fc_pstring,
										 log_parameter_max_length_on_error
										 + 2 * MAX_MULTIBYTE_CHAR_LEN);
						}

						MemoryContextSwitchTo(fc_oldcxt);
					}
					if (fc_pstring != fc_pbuf.data)
						pfree(fc_pstring);
				}
			}
			else if (fc_pformat == 1)	/* 二进制模式 */
			{
				Oid			fc_typreceive;
				Oid			fc_typioparam;
				StringInfo	fc_bufptr;

				/*
				 * 调用参数类型的二进制输入转换器
				 */
				getTypeBinaryInputInfo(fc_ptype, &fc_typreceive, &fc_typioparam);

				if (fc_isNull)
					fc_bufptr = NULL;
				else
					fc_bufptr = &fc_pbuf;

				fc_pval = OidReceiveFunctionCall(fc_typreceive, fc_bufptr, fc_typioparam, -1);

				/* 如果没有读取完整个缓冲区则会出问题 */
				if (!fc_isNull && fc_pbuf.cursor != fc_pbuf.len)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
							 errmsg("incorrect binary data format in bind parameter %d",
									fc_paramno + 1)));
			}
			else
			{
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("unsupported format code: %d",
								fc_pformat)));
				fc_pval = 0;		/* 保持编译器安静 */
			}

			/* 恢复消息缓冲区内容 */
			if (!fc_isNull)
				fc_pbuf.data[fc_plength] = fc_csave;

			fc_params->params[fc_paramno].value = fc_pval;
			fc_params->params[fc_paramno].isnull = fc_isNull;

			/*
			 * 我们将参数标记为 CONST。这确保任何自定义计划
			 * 完全使用参数值。
			 */
			fc_params->params[fc_paramno].pflags = PARAM_FLAG_CONST;
			fc_params->params[fc_paramno].ptype = fc_ptype;
		}

		/* 弹出每个参数的错误回调 */
		error_context_stack = error_context_stack->previous;

		/*
		 * 一旦收到所有参数，准备在未来的错误中打印它们，
		 * 如果配置为这样做。（这保存在 portal 中，以便在稍后执行查询时出现。）
		 */
		if (log_parameter_max_length_on_error != 0)
			fc_params->paramValuesStr =
				BuildParamLogString(fc_params,
									fc_knownTextValues,
									log_parameter_max_length_on_error);
	}
	else
		fc_params = NULL;

	/* 完成在门户上下文中存储内容 */
	MemoryContextSwitchTo(fc_oldContext);

	/*
	 * 设置另一个错误回调，以便在 BIND 处理的其余部分出现错误时
	 * 记录所有参数。
	 */
	fc_params_data.portalName = fc_portal->name;
	fc_params_data.params = fc_params;
	fc_params_errcxt.previous = error_context_stack;
	fc_params_errcxt.callback = ParamsErrorCallback;
	fc_params_errcxt.arg = (void *) &fc_params_data;
	error_context_stack = &fc_params_errcxt;

	/* 获取结果格式代码 */
	fc_numRFormats = pq_getmsgint(fc_input_message, 2);
	if (fc_numRFormats > 0)
	{
		fc_rformats = (int16 *) palloc(fc_numRFormats * sizeof(int16));
		for (int fc_i = 0; fc_i < fc_numRFormats; fc_i++)
			fc_rformats[fc_i] = pq_getmsgint(fc_input_message, 2);
	}

	pq_getmsgend(fc_input_message);

	/*
	 * 从 CachedPlanSource 获取计划。任何来自（重新）规划的杂物将
	 * 在 MessageContext 中生成。计划引用计数将分配给门户，因此
	 * 它将在门户销毁时释放。
	 */
	fc_cplan = GetCachedPlan(fc_psrc, fc_params, NULL, NULL);

	/*
	 * 现在我们可以定义门户。
	 *
	 * 请勿在上述 GetCachedPlan 调用与此处之间放置可能抛出错误的任何代码。
	 */
	PortalDefineQuery(fc_portal,
					  fc_saved_stmt_name,
					  fc_query_string,
					  fc_psrc->commandTag,
					  fc_cplan->stmt_list,
					  fc_cplan);

	/* 完成用于参数 I/O 和解析/规划的快照 */
	if (fc_snapshot_set)
		PopActiveSnapshot();

	/*
	 * 我们准备开始门户执行。
	 */
	PortalStart(fc_portal, fc_params, 0, InvalidSnapshot);

	/*
	 * 将结果格式请求应用于门户。
	 */
	PortalSetResultFormat(fc_portal, fc_numRFormats, fc_rformats);

	/*
	 * 完成绑定；移除参数错误回调。稍后发出的条目独立决定是否记录参数。
	 */
	error_context_stack = error_context_stack->previous;

	/*
	 * 发送 BindComplete。
	 */
	if (whereToSendOutput == DestRemote)
		pq_putemptymessage('2');

	/*
	 * 如果合适则记录持续时间。
	 */
	switch (check_log_duration(fc_msec_str, false))
	{
		case 1:
			ereport(LOG,
					(errmsg("duration: %s ms", fc_msec_str),
					 errhidestmt(true)));
			break;
		case 2:
			ereport(LOG,
					(errmsg("duration: %s ms  bind %s%s%s: %s",
							fc_msec_str,
							*fc_stmt_name ? fc_stmt_name : "<unnamed>",
							*fc_portal_name ? "/" : "",
							*fc_portal_name ? fc_portal_name : "",
							fc_psrc->query_string),
					 errhidestmt(true),
					 fc_errdetail_params(fc_params)));
			break;
	}

	if (fc_save_log_statement_stats)
		ShowUsage("BIND MESSAGE STATISTICS");

	debug_query_string = NULL;
}

/*
 * exec_execute_message
 *
 * 处理门户的“Execute”消息
 */
static void fc_exec_execute_message(const char *fc_portal_name, long fc_max_rows)
{
	CommandDest fc_dest;
	DestReceiver *fc_receiver;
	Portal		fc_portal;
	bool		fc_completed;
	QueryCompletion fc_qc;
	const char *fc_sourceText;
	const char *fc_prepStmtName;
	ParamListInfo fc_portalParams;
	bool		fc_save_log_statement_stats = log_statement_stats;
	bool		fc_is_xact_command;
	bool		fc_execute_is_fetch;
	bool		fc_was_logged = false;
	char		fc_msec_str[32];
	ParamsErrorCbData fc_params_data;
	ErrorContextCallback fc_params_errcxt;
	ListCell   *fc_lc;

	/* 调整目标以告诉 printtup.c 应该怎么做 */
	fc_dest = whereToSendOutput;
	if (fc_dest == DestRemote)
		fc_dest = DestRemoteExecute;

	fc_portal = GetPortalByName(fc_portal_name);
	if (!PortalIsValid(fc_portal))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_CURSOR),
				 errmsg("portal \"%s\" does not exist", fc_portal_name)));

	/*
	 * 如果原始查询是空字符串，则只需返回
	 * EmptyQueryResponse。
	 */
	if (fc_portal->commandTag == CMDTAG_UNKNOWN)
	{
		Assert(fc_portal->stmts == NIL);
		NullCommand(fc_dest);
		return;
	}

	/* 门户是否包含事务命令？ */
	fc_is_xact_command = fc_IsTransactionStmtList(fc_portal->stmts);

	/*
	 * 我们必须将 sourceText 和 prepStmtName 复制到 MessageContext，以防
	 * 门户在 finish_xact_command 期间被销毁。但我们不复制 portalParams，
	 * 更倾向于不在这种情况下打印它们。
	 */
	fc_sourceText = pstrdup(fc_portal->sourceText);
	if (fc_portal->prepStmtName)
		fc_prepStmtName = pstrdup(fc_portal->prepStmtName);
	else
		fc_prepStmtName = "<unnamed>";
	fc_portalParams = fc_portal->portalParams;

	/*
	 * 向各种监控设施报告查询。
	 */
	debug_query_string = fc_sourceText;

	pgstat_report_activity(STATE_RUNNING, fc_sourceText);

	foreach(fc_lc, fc_portal->stmts)
	{
		PlannedStmt *fc_stmt = lfirst_node(PlannedStmt, fc_lc);

		if (fc_stmt->queryId != UINT64CONST(0))
		{
			pgstat_report_query_id(fc_stmt->queryId, false);
			break;
		}
	}

	set_ps_display(GetCommandTagName(fc_portal->commandTag));

	if (fc_save_log_statement_stats)
		ResetUsage();

	BeginCommand(fc_portal->commandTag, fc_dest);

	/*
	 * 在 MessageContext 中创建目标接收器（我们不希望它在事务上下文中，
	 * 因为如果门户包含 VACUUM，它可能会被删除）。
	 */
	fc_receiver = CreateDestReceiver(fc_dest);
	if (fc_dest == DestRemoteExecute)
		SetRemoteDestReceiverParams(fc_receiver, fc_portal);

	/*
	 * 确保我们在事务命令中（这通常应该由于先前的 BIND 而已是这样）。
	 */
	fc_start_xact_command();

	/*
	 * 如果我们对现有门户重新发出 Execute 协议请求，
	 * 那么我们只是在获取更多行，而不是从头完全重新执行查询。
	 * 对于 v3 门户，atStart 永远不会重置，因此我们安全使用此检查。
	 */
	fc_execute_is_fetch = !fc_portal->atStart;

	/* 如果由 log_statement 规定，则立即记录 */
	if (fc_check_log_statement(fc_portal->stmts))
	{
		ereport(LOG,
				(errmsg("%s %s%s%s: %s",
						fc_execute_is_fetch ?
						_("execute fetch from") :
						_("execute"),
						fc_prepStmtName,
						*fc_portal_name ? "/" : "",
						*fc_portal_name ? fc_portal_name : "",
						fc_sourceText),
				 errhidestmt(true),
				 fc_errdetail_params(fc_portalParams)));
		fc_was_logged = true;
	}

	/*
	 * 如果我们处于中止事务状态，唯一可实际运行的门户是包含
	 * COMMIT 或 ROLLBACK 命令的那些。
	 */
	if (IsAbortedTransactionBlockState() &&
		!fc_IsTransactionExitStmtList(fc_portal->stmts))
		ereport(ERROR,
				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
				 errmsg("current transaction is aborted, "
						"commands ignored until end of transaction block"),
				 fc_errdetail_abort()));

	/* 在开始执行之前检查取消信号 */
	CHECK_FOR_INTERRUPTS();

	/*
	 * 可以运行门户。设置错误回调以记录参数。
	 * 参数必须在绑定阶段保存。
	 */
	fc_params_data.portalName = fc_portal->name;
	fc_params_data.params = fc_portalParams;
	fc_params_errcxt.previous = error_context_stack;
	fc_params_errcxt.callback = ParamsErrorCallback;
	fc_params_errcxt.arg = (void *) &fc_params_data;
	error_context_stack = &fc_params_errcxt;

	if (fc_max_rows <= 0)
		fc_max_rows = FETCH_ALL;

	fc_completed = PortalRun(fc_portal,
						  fc_max_rows,
						  true, /* 始终是顶层 */
						  true, /* ignored */
						  fc_receiver,
						  fc_receiver,
						  &fc_qc);

	fc_receiver->rDestroy(fc_receiver);

	/* 完成执行；移除参数错误回调 */
	error_context_stack = error_context_stack->previous;

	if (fc_completed)
	{
		if (fc_is_xact_command || (MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT))
		{
			/*
			 * 如果这是一个事务控制语句，则提交它。我们
			 * 将为下一个命令（如果有）开始一个新的 xact 命令。
			 * 同样，如果该语句需要立即提交。没有
			 * 这一规定，我们不会强制提交，直到接收到 Sync，
			 * 这会造成如果客户端尝试
			 * 流水线立即提交语句时的危险。
			 */
			fc_finish_xact_command();

			/*
			 * 这些命令通常没有任何参数，即使有一个参数
			 * 我们现在也无法打印它，因为在 finish_xact_command 中存储内容
			 * 已经消失了。所以假装没有。
			 */
			fc_portalParams = NULL;
		}
		else
		{
			/*
			 * 每个查询后我们都需要调用 CommandCounterIncrement，除了启动或结束事务块的查询。
			 */
			CommandCounterIncrement();

			/*
			 * 每当我们完成一个 Execute 消息而没有立即提交事务时，设置 XACT_FLAGS_PIPELINING。
			 */
			MyXactFlags |= XACT_FLAGS_PIPELINING;

			/*
			 * 每当我们完成一个 Execute 消息时，禁用语句超时。
			 * 下一条协议消息将启动新的超时。
			 */
			fc_disable_statement_timeout();
		}

		/* 向客户端发送适当的 CommandComplete */
		EndCommand(&fc_qc, fc_dest, false);
	}
	else
	{
		/* 门户运行未完成，因此发送 PortalSuspended */
		if (whereToSendOutput == DestRemote)
			pq_putemptymessage('s');

		/*
		 * 每当我们挂起一个 Execute 消息时，也设置 XACT_FLAGS_PIPELINING。
		 */
		MyXactFlags |= XACT_FLAGS_PIPELINING;
	}

	/*
	 * 如果合适则记录持续时间。
	 */
	switch (check_log_duration(fc_msec_str, fc_was_logged))
	{
		case 1:
			ereport(LOG,
					(errmsg("duration: %s ms", fc_msec_str),
					 errhidestmt(true)));
			break;
		case 2:
			ereport(LOG,
					(errmsg("duration: %s ms  %s %s%s%s: %s",
							fc_msec_str,
							fc_execute_is_fetch ?
							_("execute fetch from") :
							_("execute"),
							fc_prepStmtName,
							*fc_portal_name ? "/" : "",
							*fc_portal_name ? fc_portal_name : "",
							fc_sourceText),
					 errhidestmt(true),
					 fc_errdetail_params(fc_portalParams)));
			break;
	}

	if (fc_save_log_statement_stats)
		ShowUsage("EXECUTE MESSAGE STATISTICS");

	debug_query_string = NULL;
}

/*
 * check_log_statement
 *		确定是否应该根据 log_statement 记录命令
 *
 * stmt_list 可以是原始语法输出或一个计划的语句列表
 */ 
static bool fc_check_log_statement(List *fc_stmt_list)
{
	ListCell   *fc_stmt_item;

	if (log_statement == LOGSTMT_NONE)
		return false;
	if (log_statement == LOGSTMT_ALL)
		return true;

	/* 否则我们必须检查语句，以确定是否需要记录 */
	foreach(fc_stmt_item, fc_stmt_list)
	{
		Node	   *fc_stmt = (Node *) lfirst(fc_stmt_item);

		if (GetCommandLogLevel(fc_stmt) <= log_statement)
			return true;
	}

	return false;
}

/*
 * check_log_duration
 *		确定当前命令的持续时间是否需要记录
 *		我们还检查此事务中的此语句是否必须记录
 *		（无论其持续时间如何）。
 *
 * 返回：
 *		0 如果不需要记录
 *		1 如果只需记录持续时间
 *		2 如果需要记录持续时间和查询详细信息
 *
 * 如果需要记录，则持续时间以毫秒格式化为 msec_str[]，
 * 该数组必须是一个 32 字节的缓冲区。
 *
 * 如果调用者已经记录了查询详细信息，则 was_logged 应为 true（这
 * 实质上防止返回 2）。
 */
int check_log_duration(char *fc_msec_str, bool fc_was_logged)
{
	if (log_duration || log_min_duration_sample >= 0 ||
		log_min_duration_statement >= 0 || xact_is_sampled)
	{
		long		fc_secs;
		int			fc_usecs;
		int			fc_msecs;
		bool		fc_exceeded_duration;
		bool		fc_exceeded_sample_duration;
		bool		fc_in_sample = false;

		TimestampDifference(GetCurrentStatementStartTimestamp(),
							GetCurrentTimestamp(),
							&fc_secs, &fc_usecs);
		fc_msecs = fc_usecs / 1000;

		/*
		 * 这个看起来奇怪的测试，判断 log_min_duration_* 是否被超出，
		 * 是为了避免在非常长的持续时间下发生整数溢出：在验证它是否适合
		 * int 之前，不要计算 secs * 1000。
		 */
		fc_exceeded_duration = (log_min_duration_statement == 0 ||
							 (log_min_duration_statement > 0 &&
							  (fc_secs > log_min_duration_statement / 1000 ||
							   fc_secs * 1000 + fc_msecs >= log_min_duration_statement)));

		fc_exceeded_sample_duration = (log_min_duration_sample == 0 ||
									(log_min_duration_sample > 0 &&
									 (fc_secs > log_min_duration_sample / 1000 ||
									  fc_secs * 1000 + fc_msecs >= log_min_duration_sample)));

		/*
		 * 如果 log_statement_sample_rate = 0，则不记录。如果
		 * log_statement_sample_rate <= 1，则记录一个样本，并避免不必要的 PRNG 调用
		 * 如果 log_statement_sample_rate = 1。
		 */
		if (fc_exceeded_sample_duration)
			fc_in_sample = log_statement_sample_rate != 0 &&
				(log_statement_sample_rate == 1 ||
				 pg_prng_double(&pg_global_prng_state) <= log_statement_sample_rate);

		if (fc_exceeded_duration || fc_in_sample || log_duration || xact_is_sampled)
		{
			snprintf(fc_msec_str, 32, "%ld.%03d",
					 fc_secs * 1000 + fc_msecs, fc_usecs % 1000);
			if ((fc_exceeded_duration || fc_in_sample || xact_is_sampled) && !fc_was_logged)
				return 2;
			else
				return 1;
		}
	}

	return 0;
}

/*
 * errdetail_execute
 *
 * 添加一个 errdetail() 行，显示 EXECUTE 相关的查询（如果有）。
 * 参数是原始 parse tree 列表。
 */
static int fc_errdetail_execute(List *fc_raw_parsetree_list)
{
	ListCell   *fc_parsetree_item;

	foreach(fc_parsetree_item, fc_raw_parsetree_list)
	{
		RawStmt    *fc_parsetree = lfirst_node(RawStmt, fc_parsetree_item);

		if (IsA(fc_parsetree->stmt, ExecuteStmt))
		{
			ExecuteStmt *fc_stmt = (ExecuteStmt *) fc_parsetree->stmt;
			PreparedStatement *fc_pstmt;

			fc_pstmt = FetchPreparedStatement(fc_stmt->name, false);
			if (fc_pstmt)
			{
				errdetail("prepare: %s", fc_pstmt->plansource->query_string);
				return 0;
			}
		}
	}

	return 0;
}

/*
 * errdetail_params
 *
 * 添加一个 errdetail() 行，显示绑定参数数据（如果可用）。
 * 请注意，这仅用于语句记录，因此由 log_parameter_max_length 控制，而不是
 * log_parameter_max_length_on_error。
 */
static int fc_errdetail_params(ParamListInfo fc_params)
{
	if (fc_params && fc_params->numParams > 0 && log_parameter_max_length != 0)
	{
		char	   *fc_str;

		fc_str = BuildParamLogString(fc_params, NULL, log_parameter_max_length);
		if (fc_str && fc_str[0] != '\0')
			errdetail("parameters: %s", fc_str);
	}

	return 0;
}

/*
 * errdetail_abort
 *
 * 添加一个 errdetail() 行，显示中止原因（如果有）。
 */
static int fc_errdetail_abort(void)
{
	if (MyProc->recoveryConflictPending)
		errdetail("abort reason: recovery conflict");

	return 0;
}

/*
 * errdetail_recovery_conflict
 *
 * 添加一个 errdetail() 行，显示冲突来源。
 */
static int fc_errdetail_recovery_conflict(void)
{
	switch (RecoveryConflictReason)
	{
		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
			errdetail("User was holding shared buffer pin for too long.");
			break;
		case PROCSIG_RECOVERY_CONFLICT_LOCK:
			errdetail("User was holding a relation lock for too long.");
			break;
		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
			errdetail("User was or might have been using tablespace that must be dropped.");
			break;
		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
			errdetail("User query might have needed to see row versions that must be removed.");
			break;
		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
			errdetail("User transaction caused buffer deadlock with recovery.");
			break;
		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
			errdetail("User was connected to a database that must be dropped.");
			break;
		default:
			break;
			/* 无 errdetail */
	}

	return 0;
}

/*
 * bind_param_error_callback
 *
 * 在解析 Bind 消息中的参数时使用的错误上下文回调
 */
static void fc_bind_param_error_callback(void *fc_arg)
{
	BindParamCbData *fc_data = (BindParamCbData *) fc_arg;
	StringInfoData fc_buf;
	char	   *fc_quotedval;

	if (fc_data->paramno < 0)
		return;

	/* 如果我们有一个文本值，进行引用，并在必要时修剪 */
	if (fc_data->paramval)
	{
		initStringInfo(&fc_buf);
		appendStringInfoStringQuoted(&fc_buf, fc_data->paramval,
									 log_parameter_max_length_on_error);
		fc_quotedval = fc_buf.data;
	}
	else
		fc_quotedval = NULL;

	if (fc_data->portalName && fc_data->portalName[0] != '\0')
	{
		if (fc_quotedval)
			errcontext("portal \"%s\" parameter $%d = %s",
					   fc_data->portalName, fc_data->paramno + 1, fc_quotedval);
		else
			errcontext("portal \"%s\" parameter $%d",
					   fc_data->portalName, fc_data->paramno + 1);
	}
	else
	{
		if (fc_quotedval)
			errcontext("unnamed portal parameter $%d = %s",
					   fc_data->paramno + 1, fc_quotedval);
		else
			errcontext("unnamed portal parameter $%d",
					   fc_data->paramno + 1);
	}

	if (fc_quotedval)
		pfree(fc_quotedval);
}

/*
 * exec_describe_statement_message
 *
 * 处理预定义语句的 "Describe" 消息
 */
static void fc_exec_describe_statement_message(const char *fc_stmt_name)
{
	CachedPlanSource *fc_psrc;

	/*
	 * 启动事务命令。（注意，这通常会改变当前内存上下文。）如果我们已经在一个中，则不执行任何操作。
	 */
	fc_start_xact_command();

	/* 切换回消息上下文 */
	MemoryContextSwitchTo(MessageContext);

	/* 查找准备好的语句 */
	if (fc_stmt_name[0] != '\0')
	{
		PreparedStatement *fc_pstmt;

		fc_pstmt = FetchPreparedStatement(fc_stmt_name, true);
		fc_psrc = fc_pstmt->plansource;
	}
	else
	{
		/* 特殊情况处理未命名语句 */
		fc_psrc = unnamed_stmt_psrc;
		if (!fc_psrc)
#ifdef FDD //cppcheck
		{
#endif
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
					 errmsg("unnamed prepared statement does not exist")));
#ifdef FDD //cppcheck
			return;
		}
#endif
	}

	/* 预定义语句不应有可变的结果描述符 */
	Assert(fc_psrc->fixed_result);

	/*
	 * 如果我们处于中止的事务状态，则无法运行
	 * SendRowDescriptionMessage()，因为这需要目录访问。
	 * 因此，拒绝描述返回数据的语句。（我们不应该
	 * 拒绝所有描述，因为这可能会破坏某些
	 * 客户端发出 COMMIT 或 ROLLBACK 命令的能力，如果它们使用的代码
	 * 盲目地描述任何操作。）我们可以在不执行危险操作的情况下描述参数，
	 * 所以我们不限制这一点。
	 */
	if (IsAbortedTransactionBlockState() &&
		fc_psrc->resultDesc)
		ereport(ERROR,
				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
				 errmsg("current transaction is aborted, "
						"commands ignored until end of transaction block"),
				 fc_errdetail_abort()));

	if (whereToSendOutput != DestRemote)
		return;					/* 实际上无法执行任何操作... */

	/*
	 * 首先描述参数...
	 */
	pq_beginmessage_reuse(&row_description_buf, 't');	/* 参数描述消息类型 */
	pq_sendint16(&row_description_buf, fc_psrc->num_params);

	for (int fc_i = 0; fc_i < fc_psrc->num_params; fc_i++)
	{
		Oid			fc_ptype = fc_psrc->param_types[fc_i];

		pq_sendint32(&row_description_buf, (int) fc_ptype);
	}
	pq_endmessage_reuse(&row_description_buf);

	/*
	 * 然后发送 RowDescription 或 NoData 来描述结果...
	 */
	if (fc_psrc->resultDesc)
	{
		List	   *fc_tlist;

		/* 获取计划的主要目标列表 */
		fc_tlist = CachedPlanGetTargetList(fc_psrc, NULL);

		SendRowDescriptionMessage(&row_description_buf,
								  fc_psrc->resultDesc,
								  fc_tlist,
								  NULL);
	}
	else
		pq_putemptymessage('n');	/* NoData */
}

/*
 * exec_describe_portal_message
 *
 * 处理门户的 "Describe" 消息
 */
static void fc_exec_describe_portal_message(const char *fc_portal_name)
{
	Portal		fc_portal;

	/*
	 * 启动事务命令。（注意，这通常会改变当前内存上下文。）如果我们已经在一个中，则不执行任何操作。
	 */
	fc_start_xact_command();

	/* 切换回消息上下文 */
	MemoryContextSwitchTo(MessageContext);

	fc_portal = GetPortalByName(fc_portal_name);
	if (!PortalIsValid(fc_portal))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_CURSOR),
				 errmsg("portal \"%s\" does not exist", fc_portal_name)));

	/*
	 * 如果我们处于中止的事务状态，则无法运行
	 * SendRowDescriptionMessage()，因为这需要目录访问。
	 * 因此，拒绝描述返回数据的门户。（我们不应该只是
	 * 拒绝所有描述，因为这可能会破坏某些
	 * 客户端发出 COMMIT 或 ROLLBACK 命令的能力，如果它们使用的代码
	 * 盲目地描述任何操作。）
	 */
	if (IsAbortedTransactionBlockState() &&
		fc_portal->tupDesc)
		ereport(ERROR,
				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
				 errmsg("current transaction is aborted, "
						"commands ignored until end of transaction block"),
				 fc_errdetail_abort()));

	if (whereToSendOutput != DestRemote)
		return;					/* 实际上无法执行任何操作... */

	if (fc_portal->tupDesc)
		SendRowDescriptionMessage(&row_description_buf,
								  fc_portal->tupDesc,
								  FetchPortalTargetList(fc_portal),
								  fc_portal->formats);
	else
		pq_putemptymessage('n');	/* NoData */
}


/*
 * 用于启动/提交单个命令的便利例程。
 */
static void fc_start_xact_command(void)
{
	if (!xact_started)
	{
		StartTransactionCommand();

		xact_started = true;
	}

	/*
	 * 如果需要，则启动语句超时。请注意，这将故意
	 * 不重置已经启动的超时的时钟，以避免多次调用
	 * start_xact_command() 时的时间开销，而没有插入的 finish_xact_command()
	 * （例如 parse/bind/execute）。如果不希望这样，则必须明确禁用超时。
	 */
	fc_enable_statement_timeout();

	/* 如果需要，启动检查客户端是否已断开的超时。 */
	if (client_connection_check_interval > 0 &&
		IsUnderPostmaster &&
		MyProcPort &&
		!get_timeout_active(CLIENT_CONNECTION_CHECK_TIMEOUT))
		enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
							 client_connection_check_interval);
}

static void fc_finish_xact_command(void)
{
	/* 在每个命令后取消活动语句超时 */
	fc_disable_statement_timeout();

	if (xact_started)
	{
		CommitTransactionCommand();

#ifdef MEMORY_CONTEXT_CHECKING
		/* 检查所有在提交期间未释放的内存上下文 */
		/* （那些已经被释放的，在删除之前已被检查） */
		MemoryContextCheck(TopMemoryContext);
#endif

#ifdef SHOW_MEMORY_STATS
		/* 在每次提交后打印内存统计以跟踪泄漏 */
		MemoryContextStats(TopMemoryContext);
#endif

		xact_started = false;
	}
}


/*
 * 用于检查语句是否为我们在事务中止状态下允许的
 * 语句之一的便利例程。
 */

/* 检查一个裸的 parse tree */
static bool fc_IsTransactionExitStmt(Node *fc_parsetree)
{
	if (fc_parsetree && IsA(fc_parsetree, TransactionStmt))
	{
		TransactionStmt *fc_stmt = (TransactionStmt *) fc_parsetree;

		if (fc_stmt->kind == TRANS_STMT_COMMIT ||
			fc_stmt->kind == TRANS_STMT_PREPARE ||
			fc_stmt->kind == TRANS_STMT_ROLLBACK ||
			fc_stmt->kind == TRANS_STMT_ROLLBACK_TO)
			return true;
	}
	return false;
}

/* 测试包含 PlannedStmt 节点的列表 */
static bool fc_IsTransactionExitStmtList(List *fc_pstmts)
{
	if (list_length(fc_pstmts) == 1)
	{
		PlannedStmt *fc_pstmt = linitial_node(PlannedStmt, fc_pstmts);

		if (fc_pstmt->commandType == CMD_UTILITY &&
			fc_IsTransactionExitStmt(fc_pstmt->utilityStmt))
			return true;
	}
	return false;
}

/* 测试包含 PlannedStmt 节点的列表 */
static bool fc_IsTransactionStmtList(List *fc_pstmts)
{
	if (list_length(fc_pstmts) == 1)
	{
		PlannedStmt *fc_pstmt = linitial_node(PlannedStmt, fc_pstmts);

		if (fc_pstmt->commandType == CMD_UTILITY &&
			IsA(fc_pstmt->utilityStmt, TransactionStmt))
			return true;
	}
	return false;
}

/* 释放任何现有的无名预备语句 */
static void fc_drop_unnamed_stmt(void)
{
	/* 为了防止在错误情况下出现悬空指针 */
	if (unnamed_stmt_psrc)
	{
		CachedPlanSource *fc_psrc = unnamed_stmt_psrc;

		unnamed_stmt_psrc = NULL;
		DropCachedPlan(fc_psrc);
	}
}


/* --------------------------------
 *		在 PostgresMain() 中使用的信号处理程序例程
 * --------------------------------
 */

/*
 * 当 postmaster 发送 SIGQUIT 信号时发生 quickdie() 函数。
 *
 * 要么是某个后端崩溃了，要么我们被告知“立即”关闭；因此我们需要停止正在做的事情并退出。
 */
void quickdie(SIGNAL_ARGS)
{
	sigaddset(&BlockSig, SIGQUIT);	/* 防止嵌套调用 */
	PG_SETMASK(&BlockSig);

	/*
	 * 在退出时防止中断；虽然我们刚阻止了会排队新中断的信号，但可能已经有一个挂起的信号。我们不想让 quickdie() 降级为一个普通的查询取消。
	 */
	HOLD_INTERRUPTS();

	/*
	 * 如果我们正在中止客户端认证，不要冒险尝试向客户端发送任何东西；我们可能会违反协议，更不用说我们可能已经中断了 OpenSSL 或某些认证库的内部操作。
	 */
	if (ClientAuthInProgress && whereToSendOutput == DestRemote)
		whereToSendOutput = DestNone;

	/*
	 * 在退出之前通知客户端，以提供发生了什么的线索。
	 *
	 * 从信号处理程序调用 ereport() 是可疑的。它显然不是异步信号安全的。但尝试一下似乎比突然断开连接并让客户端不知所措要好得多。我们尝试发送消息时崩溃或挂起是远程可能的，但是接收到 SIGQUIT 是一个信号，表明事情已经出现严重问题，因此损失不大。假设 postmaster 仍在运行，如果我们出于某种原因被卡住，它很快会用 SIGKILL 来终止我们。
	 *
	 * 我们可以做的一件事情是清除错误上下文栈，这样上下文回调便不会被调用。这里可以访问的代码少得多，而且上下文信息不太可能与 SIGQUIT 报告相关。
	 */
	error_context_stack = NULL;

	/*
	 * 在响应 postmaster 发出的信号时，我们仅将消息发送给客户端；发送到服务器日志只会产生日志垃圾邮件，而且还需要更多代码，这些代码我们必须希望它能在信号处理程序中工作。
	 *
	 * 理想情况下，这些应该是 ereport(FATAL)，但那样我们就无法获得控制权以强制正确的进程退出类型。
	 */
	switch (GetQuitSignalReason())
	{
		case PMQUIT_NOT_SENT:
			/* 嗯，SIGQUIT 突然到来 */
			ereport(WARNING,
					(errcode(ERRCODE_ADMIN_SHUTDOWN),
					 errmsg("terminating connection because of unexpected SIGQUIT signal")));
			break;
		case PMQUIT_FOR_CRASH:
			/* 正在进行崩溃和重启循环 */
			ereport(WARNING_CLIENT_ONLY,
					(errcode(ERRCODE_CRASH_SHUTDOWN),
					 errmsg("terminating connection because of crash of another server process"),
					 errdetail("The postmaster has commanded this server process to roll back"
							   " the current transaction and exit, because another"
							   " server process exited abnormally and possibly corrupted"
							   " shared memory."),
					 errhint("In a moment you should be able to reconnect to the"
							 " database and repeat your command.")));
			break;
		case PMQUIT_FOR_STOP:
			/* 立即模式停止 */
			ereport(WARNING_CLIENT_ONLY,
					(errcode(ERRCODE_ADMIN_SHUTDOWN),
					 errmsg("terminating connection due to immediate shutdown command")));
			break;
	}

	/*
	 * 我们不希望运行 proc_exit() 或 atexit() 回调 -- 我们在这里
	 * 是因为共享内存可能已经损坏，所以我们不想尝试
	 * 清理我们的事务。只需把窗户钉死，然后离开这个地方。
	 * 回调从信号处理程序中运行也是不安全的。
	 *
	 * 请注意，我们使用 _exit(2) 而不是 _exit(0)。这是为了强迫
	 * 后台工作进程进入系统重置循环，如果有人向随机的
	 * 后端发送手动 SIGQUIT。这是必要的，正因为如此我们没有
	 * 清理我们的共享内存状态。（pmsignal.c 中的 "死人开关"
	 * 机制应该确保后台工作进程也将此视为故障，但反正
	 * 重复确认也没有坏处。）
	 */
	_exit(2);
}

/*
 * 来自 postmaster 的关闭信号：在最合适的时间中止事务并退出
 */
void die(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	/* 不要干扰 proc_exit 的操作 */
	if (!proc_exit_inprogress)
	{
		InterruptPending = true;
		ProcDiePending = true;
	}

	/* 用于累积统计系统 */
	pgStatSessionEndCause = DISCONNECT_KILLED;

	/* 如果我们还在这里，唤醒一切正在等待进程闩锁的东西 */
	SetLatch(MyLatch);

	/*
	 * 如果我们处于单用户模式，我们希望立即退出 - 我们不能依靠闩锁，因为当 stdin/stdout 是文件时它们无法工作。
	 * 这相当丑陋，但不太可能值得为单用户模式投入更多的精力。
	 */
	if (DoingCommandRead && whereToSendOutput != DestRemote)
		ProcessInterrupts();

	errno = fc_save_errno;
}

/*
 * 来自 postmaster 的查询取消信号：在最合适的时间中止当前事务
 */
void StatementCancelHandler(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	/*
	 * 不要干扰 proc_exit 的操作
	 */
	if (!proc_exit_inprogress)
	{
		InterruptPending = true;
		QueryCancelPending = true;
	}

	/* 如果我们还在这里，唤醒一切正在等待进程闩锁的东西 */
	SetLatch(MyLatch);

	errno = fc_save_errno;
}

/* 浮点异常的信号处理程序 */
void FloatExceptionHandler(SIGNAL_ARGS)
{
	/* 我们不打算返回，因此无需保存 errno */
	ereport(ERROR,
			(errcode(ERRCODE_FLOATING_POINT_EXCEPTION),
			 errmsg("floating-point exception"),
			 errdetail("An invalid floating-point operation was signaled. "
					   "This probably means an out-of-range result or an "
					   "invalid operation, such as division by zero.")));
}

/*
 * RecoveryConflictInterrupt：在接收到 SIGUSR1 后处理恢复冲突的非线性部分。
 * 设计上与 die() 和 StatementCancelHandler() 类似。仅由在恢复期间开始事务的普通用户后端调用。
 */
void RecoveryConflictInterrupt(ProcSignalReason fc_reason)
{
	int			fc_save_errno = errno;

	/*
	 * 不要干扰 proc_exit 的操作
	 */
	if (!proc_exit_inprogress)
	{
		RecoveryConflictReason = fc_reason;
		switch (fc_reason)
		{
			case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:

				/*
				 * 如果我们不在等待锁，我们就永远不会死锁。
				 */
				if (!IsWaitingForLock())
					return;

				/* 故意跳过以检查等待 pin */
				/* FALLTHROUGH */

			case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:

				/*
				 * 如果请求了 PROCSIG_RECOVERY_CONFLICT_BUFFERPIN，但我们没有阻止 Startup 进程，则没有更多可以做的。
				 *
				 * 当请求 PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK 时，如果我们正在等待锁并且启动进程不在等待缓冲区锁（即，也在等待锁），我们设置标志，以便 ProcSleep() 将检查死锁。
				 */
				if (!HoldingBufferPinThatDelaysRecovery())
				{
					if (fc_reason == PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK &&
						GetStartupBufferPinWaitBufId() < 0)
						CheckDeadLockAlert();
					return;
				}

				MyProc->recoveryConflictPending = true;

				/* 故意跳过以处理错误 */
				/* FALLTHROUGH */

			case PROCSIG_RECOVERY_CONFLICT_LOCK:
			case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
			case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:

				/*
				 * 如果我们不再处于事务中，则忽略。
				 */
				if (!IsTransactionOrTransactionBlock())
					return;

				/*
				 * 如果我们可以仅中止当前子事务，那么我们就可以
				 * 抛出一个ERROR来解决冲突。否则
				 * 进入FATAL情况。
				 *
				 * XXX 其他可以仅抛出ERROR的情况可能是
				 * PROCSIG_RECOVERY_CONFLICT_LOCK，如果在
				 * 父事务中没有持有的锁
				 *
				 * PROCSIG_RECOVERY_CONFLICT_SNAPSHOT，如果在父事务中没有持有快照
				 * 而且事务不是
				 * 事务快照模式
				 *
				 * PROCSIG_RECOVERY_CONFLICT_TABLESPACE，如果在父事务中没有打开临时文件或
				 * 游标
				 */
				if (!IsSubTransaction())
				{
					/*
					 * 如果我们已经中止，那么我们不再需要取消。
					 * 我们在这里执行此操作，因为我们不希望忽略已中止
					 * 的子事务，这必须引起FATAL，目前。
					 */
					if (IsAbortedTransactionBlockState())
						return;

					RecoveryConflictPending = true;
					QueryCancelPending = true;
					InterruptPending = true;
					break;
				}

				/*故意通过以会话取消为目的的空降*/

				/* FALLTHROUGH */

			case PROCSIG_RECOVERY_CONFLICT_DATABASE:
				RecoveryConflictPending = true;
				ProcDiePending = true;
				InterruptPending = true;
				break;

			default:
				elog(FATAL, "unrecognized conflict mode: %d",
					 (int) fc_reason);
		}

		Assert(RecoveryConflictPending && (QueryCancelPending || ProcDiePending));

		/*
		 * 除了数据库之外的所有冲突都会导致动态错误，其中
		 * 命令或事务可以在稍后重试，有成功的潜力。
		 * 不需要重置这个，因为不可重试的冲突错误目前是致命的。
		 */

		if (fc_reason == PROCSIG_RECOVERY_CONFLICT_DATABASE)
			RecoveryConflictRetryable = false;
	}

	/*
	 * 设置进程锁。这个函数实质上模拟了信号处理程序
	 * like die() 和 StatementCancelHandler()，也似乎明智
	 * 地以类似的方式进行处理。
	 */

	SetLatch(MyLatch);

	errno = fc_save_errno;
}

/*
 * ProcessInterrupts：CHECK_FOR_INTERRUPTS()宏的外部部分
 *
 * 如果有中断条件待处理，并且安全处理该条件，
 * 则清除标志并接受中断。 仅在
 * InterruptPending 为 true 时调用。
 *
 * 注意：如果 INTERRUPTS_CAN_BE_PROCESSED() 为 true，则 ProcessInterrupts
 * 保证在返回之前清除 InterruptPending 标志。
 * （这并不等同于保证我们返回时它仍然清除；可能会出现另一个中断。
 * 但我们承诺任何先前的中断将会被处理。）
 */

void ProcessInterrupts(void)
{
	/* 现在可以接受任何中断吗？ */
	if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
		return;
	InterruptPending = false;

	if (ProcDiePending)
	{
		ProcDiePending = false;
		QueryCancelPending = false; /* ProcDie 优先于 QueryCancel */
		LockErrorCleanup();
		/* 如同 quickdie，在身份验证期间不要冒险发送到客户端 */
		if (ClientAuthInProgress && whereToSendOutput == DestRemote)
			whereToSendOutput = DestNone;
		if (ClientAuthInProgress)
			ereport(FATAL,
					(errcode(ERRCODE_QUERY_CANCELED),
					 errmsg("canceling authentication due to timeout")));
		else if (IsAutoVacuumWorkerProcess())
			ereport(FATAL,
					(errcode(ERRCODE_ADMIN_SHUTDOWN),
					 errmsg("terminating autovacuum process due to administrator command")));
		else if (IsLogicalWorker())
			ereport(FATAL,
					(errcode(ERRCODE_ADMIN_SHUTDOWN),
					 errmsg("terminating logical replication worker due to administrator command")));
		else if (IsLogicalLauncher())
		{
			ereport(DEBUG1,
					(errmsg_internal("logical replication launcher shutting down")));

			/*
			 * 逻辑复制启动器可以随时停止。
			 * 使用退出状态 1，以便重新启动后台工作程序。
			 */
			proc_exit(1);
		}
		else if (RecoveryConflictPending && RecoveryConflictRetryable)
		{
			pgstat_report_recovery_conflict(RecoveryConflictReason);
			ereport(FATAL,
					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
					 errmsg("terminating connection due to conflict with recovery"),
					 fc_errdetail_recovery_conflict()));
		}
		else if (RecoveryConflictPending)
		{
			/* 目前只有一个不可重试的恢复冲突 */
			Assert(RecoveryConflictReason == PROCSIG_RECOVERY_CONFLICT_DATABASE);
			pgstat_report_recovery_conflict(RecoveryConflictReason);
			ereport(FATAL,
					(errcode(ERRCODE_DATABASE_DROPPED),
					 errmsg("terminating connection due to conflict with recovery"),
					 fc_errdetail_recovery_conflict()));
		}
		else if (IsBackgroundWorker)
			ereport(FATAL,
					(errcode(ERRCODE_ADMIN_SHUTDOWN),
					 errmsg("terminating background worker \"%s\" due to administrator command",
							MyBgworkerEntry->bgw_type)));
		else
			ereport(FATAL,
					(errcode(ERRCODE_ADMIN_SHUTDOWN),
					 errmsg("terminating connection due to administrator command")));
	}

	if (CheckClientConnectionPending)
	{
		CheckClientConnectionPending = false;

		/*
		 * 检查连接是否丢失，如果仍然配置则重新设定，
		 * 但如果我们回到 DoingCommandRead 状态就不需要。
		 * 我们不想唤醒空闲会话，它们已经知道如何检测丢失的
		 * 连接。
		 */
		if (!DoingCommandRead && client_connection_check_interval > 0)
		{
			if (!pq_check_connection())
				ClientConnectionLost = true;
			else
				enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
									 client_connection_check_interval);
		}
	}

	if (ClientConnectionLost)
	{
		QueryCancelPending = false; /* 丢失的连接优先于 QueryCancel */
		LockErrorCleanup();
		/* 不要发送到客户端，我们已经知道连接已断开。 */
		whereToSendOutput = DestNone;
		ereport(FATAL,
				(errcode(ERRCODE_CONNECTION_FAILURE),
				 errmsg("connection to client lost")));
	}

	/*
	 * 如果在等待客户端输入时发生恢复冲突，
	 * 客户端大概只是静止在一个事务中，
	 * 阻止恢复进度的进行。 终止连接以
	 * 使其脱离。
	 */
	if (RecoveryConflictPending && DoingCommandRead)
	{
		QueryCancelPending = false; /* 这优先于 QueryCancel */
		RecoveryConflictPending = false;
		LockErrorCleanup();
		pgstat_report_recovery_conflict(RecoveryConflictReason);
		ereport(FATAL,
				(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
				 errmsg("terminating connection due to conflict with recovery"),
				 fc_errdetail_recovery_conflict(),
				 errhint("In a moment you should be able to reconnect to the"
						 " database and repeat your command.")));
	}

	/*
	 * 在从客户端读取输入时，不允许查询取消中断，
	 * 因为我们可能会在 FE/BE 协议中失去同步。
	 * （Die 中断是可以的，因为在这种情况下，我们不会从
	 * 客户端读取任何进一步的消息。）
	 */
	if (QueryCancelPending && QueryCancelHoldoffCount != 0)
	{
		/*
		 * 重新设置 InterruptPending，以便我们在完成读取消息后
		 * 尽快处理取消请求。 （XXX 这真的很丑：它使得
		 * INTERRUPTS_CAN_BE_PROCESSED() 变得复杂，并意味着我们
		 * 不能直接使用该宏作为此函数中的初始测试，
		 * 这意味着这段代码也为其他错误的出现创造了机会。）
		 */
		InterruptPending = true;
	}
	else if (QueryCancelPending)
	{
		bool		fc_lock_timeout_occurred;
		bool		fc_stmt_timeout_occurred;

		QueryCancelPending = false;

		/*
		 * 如果 LOCK_TIMEOUT 和 STATEMENT_TIMEOUT 指示符都被设置，
		 * 我们需要清除两者，因此始终提取两者。
		 */
		fc_lock_timeout_occurred = get_timeout_indicator(LOCK_TIMEOUT, true);
		fc_stmt_timeout_occurred = get_timeout_indicator(STATEMENT_TIMEOUT, true);

		/*
		 * 如果两者都被设置，我们想要报告较早完成的超时；
		 * 如果机器足够慢，以至于第二个超时在我们到达这里之前触发，这确保
		 * 一致行为。平局任意让给报告锁超时。
		 */
		if (fc_lock_timeout_occurred && fc_stmt_timeout_occurred &&
			get_timeout_finish_time(STATEMENT_TIMEOUT) < get_timeout_finish_time(LOCK_TIMEOUT))
			fc_lock_timeout_occurred = false;	/* 报告语句超时 */

		if (fc_lock_timeout_occurred)
		{
			LockErrorCleanup();
			ereport(ERROR,
					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
					 errmsg("canceling statement due to lock timeout")));
		}
		if (fc_stmt_timeout_occurred)
		{
			LockErrorCleanup();
			ereport(ERROR,
					(errcode(ERRCODE_QUERY_CANCELED),
					 errmsg("canceling statement due to statement timeout")));
		}
		if (IsAutoVacuumWorkerProcess())
		{
			LockErrorCleanup();
			ereport(ERROR,
					(errcode(ERRCODE_QUERY_CANCELED),
					 errmsg("canceling autovacuum task")));
		}
		if (RecoveryConflictPending)
		{
			RecoveryConflictPending = false;
			LockErrorCleanup();
			pgstat_report_recovery_conflict(RecoveryConflictReason);
			ereport(ERROR,
					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
					 errmsg("canceling statement due to conflict with recovery"),
					 fc_errdetail_recovery_conflict()));
		}

		/*
		 * 如果我们正在从客户端读取命令，只需忽略取消请求 ---
		 * 发送额外的错误消息不会有什么成效。
		 * 否则，继续抛出错误。
		 */
		if (!DoingCommandRead)
		{
			LockErrorCleanup();
			ereport(ERROR,
					(errcode(ERRCODE_QUERY_CANCELED),
					 errmsg("canceling statement due to user request")));
		}
	}

	if (IdleInTransactionSessionTimeoutPending)
	{
		/*
		 * 如果 GUC 已重置为零，则忽略信号。
		 * 这很重要，因为 GUC 更新本身不会禁用任何待处理的
		 * 中断。
		 */
		if (IdleInTransactionSessionTimeout > 0)
			ereport(FATAL,
					(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
					 errmsg("terminating connection due to idle-in-transaction timeout")));
		else
			IdleInTransactionSessionTimeoutPending = false;
	}

	if (IdleSessionTimeoutPending)
	{
		/* 如上所述，如果 GUC 已重置为零，则忽略信号。 */
		if (IdleSessionTimeout > 0)
			ereport(FATAL,
					(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
					 errmsg("terminating connection due to idle-session timeout")));
		else
			IdleSessionTimeoutPending = false;
	}

	/*
	 * 如果有待处理的统计信息更新，并且我们当前确实处于空闲状态
	 *（匹配 PostgresMain() 中的条件），现在报告统计信息。
	 */
	if (IdleStatsUpdateTimeoutPending &&
		DoingCommandRead && !IsTransactionOrTransactionBlock())
	{
		IdleStatsUpdateTimeoutPending = false;
		pgstat_report_stat(true);
	}

	if (ProcSignalBarrierPending)
		ProcessProcSignalBarrier();

	if (ParallelMessagePending)
		HandleParallelMessages();

	if (LogMemoryContextPending)
		ProcessLogMemoryContextInterrupt();
}


/*
 * IA64特定代码用于获取 AR.BSP 寄存器以进行堆栈深度检查。
 *
 * 我们目前在这里支持 gcc、icc 和 HP-UX 的本机编译器。
 *
 * 注意：虽然 icc 在 x86[_64] 上接受 gcc asm 块，但在
 * ia64 上并不适用（至少在 12.x 之前的 icc 版本中）。
 * 所以我们必须为它维护一个单独的实现。
 */
#if defined(__ia64__) || defined(__ia64)

#if defined(__hpux) && !defined(__GNUC__) && !defined(__INTEL_COMPILER)
/* 假设它是 HP-UX 本机编译器 */
#include <ia64/sys/inline.h>
#define ia64_get_bsp() ((char *) (_Asm_mov_from_ar(_AREG_BSP, _NO_FENCE)))
#elif defined(__INTEL_COMPILER)
/* icc */
#include <asm/ia64regs.h>
#define ia64_get_bsp() ((char *) __getReg(_IA64_REG_AR_BSP))
#else
/* gcc */
static __inline__ char *
ia64_get_bsp(void)
{
	char	   *ret;

	/* ;; 是一个“停止”，在获取 BSP 之前似乎是必需的 */
	__asm__ __volatile__(
						 ";;\n"
						 "	mov	%0=ar.bsp	\n"
:						 "=r"(ret));

	return ret;
}
#endif
#endif							/* IA64 */


/*
 * set_stack_base：为堆栈深度检查建立参考点
 *
 * 返回旧的参考点（如果有）。
 */
pg_stack_base_t
set_stack_base(void)
{
#ifndef HAVE__BUILTIN_FRAME_ADDRESS
	char		stack_base;
#endif
	pg_stack_base_t fc_old;

#if defined(__ia64__) || defined(__ia64)
	old.stack_base_ptr = stack_base_ptr;
	old.register_stack_base_ptr = register_stack_base_ptr;
#else
	fc_old = stack_base_ptr;
#endif

	/*
	 * 为堆栈深度检查设置参考点。在最近的 gcc 中，我们使用
	 * __builtin_frame_address() 来避免关于将局部
	 * 变量的地址存储在长期变量中的警告。
	 */
#ifdef HAVE__BUILTIN_FRAME_ADDRESS
	stack_base_ptr = __builtin_frame_address(0);
#else
	stack_base_ptr = &stack_base;
#endif
#if defined(__ia64__) || defined(__ia64)
	register_stack_base_ptr = ia64_get_bsp();
#endif

	return fc_old;
}


/********
 * restore_stack_base: 恢复栈深度检查的参考点
 *
 * 在调用 set_stack_base() 之后可以使用此函数来恢复旧值。此功能
 * 当前仅在 PL/Java 中使用。当 PL/Java 从不同线程调用后台函数时，
 * 该线程的栈位于与主线程的栈不同的位置，因此在调用之前设置基指针，
 * 并在调用之后恢复它。
 */
void restore_stack_base(pg_stack_base_t fc_base)
{
#if defined(__ia64__) || defined(__ia64)
	stack_base_ptr = base.stack_base_ptr;
	register_stack_base_ptr = base.register_stack_base_ptr;
#else
	stack_base_ptr = fc_base;
#endif
}

/*
 * check_stack_depth/stack_is_too_deep: 检查过深的递归
 *
 * 此函数应在任何可能递归到足够深以导致栈溢出的递归例程中的某个
 * 地方调用。大多数 Unixen 将栈溢出视为不可恢复的 SIGSEGV，因此我们
 * 希望在达到硬件限制之前自行报告错误。
 *
 * check_stack_depth() 仅以简要方式抛出错误。stack_is_too_deep()
 * 可以由希望自行处理错误条件的代码使用。
 */
void check_stack_depth(void)
{
	if (stack_is_too_deep())
	{
		ereport(ERROR,
				(errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
				 errmsg("stack depth limit exceeded"),
				 errhint("Increase the configuration parameter \"max_stack_depth\" (currently %dkB), "
						 "after ensuring the platform's stack depth limit is adequate.",
						 max_stack_depth)));
	}
}

bool stack_is_too_deep(void)
{
	char		fc_stack_top_loc;
	long		fc_stack_depth;

	/*
	 * 计算从参考点到我的局部变量的距离
	 */
	fc_stack_depth = (long) (stack_base_ptr - &fc_stack_top_loc);

	/*
	 * 取绝对值，因为在某些机器上栈向上增长，而在其他机器上向下增长
	 */
	if (fc_stack_depth < 0)
		fc_stack_depth = -fc_stack_depth;

	/*
	 * 有问题吗？
	 *
	 * stack_base_ptr上的测试防止我们在进程设置期间或在非后端进程中调用时出错。逻辑上应该先执行它，但将它放在这里可以避免在正常情况下浪费周期。
	 */
	if (fc_stack_depth > max_stack_depth_bytes &&
		stack_base_ptr != NULL)
		return true;

	/*
	 * 在IA64上有一个独立的“寄存器”栈，需要单独检查。为此，我们必须测量从PostgresMain到这里的“BSP”指针的变化。逻辑与上述相同，除了我们知道IA64的寄存器栈向上增长。
	 *
	 * 注意，我们假设相同的max_stack_depth适用于两个栈。
	 */
#if defined(__ia64__) || defined(__ia64)
	stack_depth = (long) (ia64_get_bsp() - register_stack_base_ptr);

	if (stack_depth > max_stack_depth_bytes &&
		register_stack_base_ptr != NULL)
		return true;
#endif							/* IA64 */

	return false;
}

/* max_stack_depth的GUC检查钩子 */
bool check_max_stack_depth(int *fc_newval, void **fc_extra, GucSource fc_source)
{
	long		fc_newval_bytes = *fc_newval * 1024L;
	long		fc_stack_rlimit = get_stack_depth_rlimit();

	if (fc_stack_rlimit > 0 && fc_newval_bytes > fc_stack_rlimit - STACK_DEPTH_SLOP)
	{
		GUC_check_errdetail("\"max_stack_depth\" must not exceed %ldkB.",
							(fc_stack_rlimit - STACK_DEPTH_SLOP) / 1024L);
		GUC_check_errhint("Increase the platform's stack depth limit via \"ulimit -s\" or local equivalent.");
		return false;
	}
	return true;
}

/* max_stack_depth的GUC分配钩子 */
void assign_max_stack_depth(int fc_newval, void *fc_extra)
{
	long		fc_newval_bytes = fc_newval * 1024L;

	max_stack_depth_bytes = fc_newval_bytes;
}

/*
 * restrict_nonsystem_relation_kind的GUC检查钩子
 */
bool check_restrict_nonsystem_relation_kind(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	char	   *fc_rawstring;
	List	   *fc_elemlist;
	ListCell   *fc_l;
	int			fc_flags = 0;

	/* 需要可修改的字符串副本 */
	fc_rawstring = pstrdup(*fc_newval);

	if (!SplitIdentifierString(fc_rawstring, ',', &fc_elemlist))
	{
		/* 列表中的语法错误 */
		GUC_check_errdetail("List syntax is invalid.");
		pfree(fc_rawstring);
		list_free(fc_elemlist);
		return false;
	}

	foreach(fc_l, fc_elemlist)
	{
		char	   *fc_tok = (char *) lfirst(fc_l);

		if (pg_strcasecmp(fc_tok, "view") == 0)
			fc_flags |= RESTRICT_RELKIND_VIEW;
		else if (pg_strcasecmp(fc_tok, "foreign-table") == 0)
			fc_flags |= RESTRICT_RELKIND_FOREIGN_TABLE;
		else
		{
			GUC_check_errdetail("Unrecognized key word: \"%s\".", fc_tok);
			pfree(fc_rawstring);
			list_free(fc_elemlist);
			return false;
		}
	}

	pfree(fc_rawstring);
	list_free(fc_elemlist);

	/* 将标志保存在*extra中，以供分配函数使用 */
	*fc_extra = malloc(sizeof(int));
	if (*fc_extra == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	*((int *) *fc_extra) = fc_flags;

	return true;
}

/*
 * restrict_nonsystem_relation_kind的GUC分配钩子
 */
void assign_restrict_nonsystem_relation_kind(const char *fc_newval, void *fc_extra)
{
	int		   *fc_flags = (int *) fc_extra;

	restrict_nonsystem_relation_kind = *fc_flags;
}

/*
 * set_debug_options --- 应用“-d N”命令行选项
 *
 * -d与设置log_min_messages不太相同，因为它启用其他输出选项。
 */
void set_debug_options(int fc_debug_flag, GucContext fc_context, GucSource fc_source)
{
	if (fc_debug_flag > 0)
	{
		char		fc_debugstr[64];

		sprintf(fc_debugstr, "debug%d", fc_debug_flag);
		SetConfigOption("log_min_messages", fc_debugstr, fc_context, fc_source);
	}
	else
		SetConfigOption("log_min_messages", "notice", fc_context, fc_source);

	if (fc_debug_flag >= 1 && fc_context == PGC_POSTMASTER)
	{
		SetConfigOption("log_connections", "true", fc_context, fc_source);
		SetConfigOption("log_disconnections", "true", fc_context, fc_source);
	}
	if (fc_debug_flag >= 2)
		SetConfigOption("log_statement", "all", fc_context, fc_source);
	if (fc_debug_flag >= 3)
		SetConfigOption("debug_print_parse", "true", fc_context, fc_source);
	if (fc_debug_flag >= 4)
		SetConfigOption("debug_print_plan", "true", fc_context, fc_source);
	if (fc_debug_flag >= 5)
		SetConfigOption("debug_print_rewritten", "true", fc_context, fc_source);
}


bool set_plan_disabling_options(const char *fc_arg, GucContext fc_context, GucSource fc_source)
{
	const char *fc_tmp = NULL;

	switch (fc_arg[0])
	{
		case 's':				/* seqscan */
			fc_tmp = "enable_seqscan";
			break;
		case 'i':				/* indexscan */
			fc_tmp = "enable_indexscan";
			break;
		case 'o':				/* indexonlyscan */
			fc_tmp = "enable_indexonlyscan";
			break;
		case 'b':				/* bitmapscan */
			fc_tmp = "enable_bitmapscan";
			break;
		case 't':				/* tidscan */
			fc_tmp = "enable_tidscan";
			break;
		case 'n':				/* nestloop */
			fc_tmp = "enable_nestloop";
			break;
		case 'm':				/* mergejoin */
			fc_tmp = "enable_mergejoin";
			break;
		case 'h':				/* hashjoin */
			fc_tmp = "enable_hashjoin";
			break;
	}
	if (fc_tmp)
	{
		SetConfigOption(fc_tmp, "false", fc_context, fc_source);
		return true;
	}
	else
		return false;
}


const char * get_stats_option_name(const char *fc_arg)
{
	switch (fc_arg[0])
	{
		case 'p':
			if (optarg[1] == 'a')	/* "parser" */
				return "log_parser_stats";
			else if (optarg[1] == 'l')	/* “planner” */
				return "log_planner_stats";
			break;

		case 'e':				/* “executor” */
			return "log_executor_stats";
			break;
	}

	return NULL;
}


/* ----------------------------------------------------------------
 * process_postgres_switches
 *	   解析后端的命令行参数
 *
 * 这被调用两次，一次是来自postmaster或命令行的“安全”选项，一次是来自客户端的启动数据包的“不安全”选项。后者的语法相同，但可能在可以做的事情上受到限制。
 *
 * argv[0]在任何情况下都被忽略（假定它是程序名称）。
 *
 * ctx是PGC_POSTMASTER用于安全选项，PGC_BACKEND用于来自客户端的不安全选项，或PGC_SU_BACKEND用于来自超级用户客户端的不安全选项。
 *
 * 如果命令行参数中存在数据库名称，它会返回到*dbname（这仅在*dbname最初为NULL时允许）。
 * ----------------------------------------------------------------
 */
void process_postgres_switches(int fc_argc, char *fc_argv[], GucContext fc_ctx,
						  const char **fc_dbname)
{
	bool		fc_secure = (fc_ctx == PGC_POSTMASTER);
	int			fc_errs = 0;
	GucSource	fc_gucsource;
	int			fc_flag;

	if (fc_secure)
	{
		fc_gucsource = PGC_S_ARGV; /* 选项来自命令行 */

		/* 如果存在，忽略初始的--single参数 */
		if (fc_argc > 1 && strcmp(fc_argv[1], "--single") == 0)
		{
			fc_argv++;
			fc_argc--;
		}
	}
	else
	{
		fc_gucsource = PGC_S_CLIENT;	/* 选项来自客户端 */
	}

#ifdef HAVE_INT_OPTERR

	/*
	 * 关闭此选项，因为它要么打印到stderr而不是我们希望的日志中，要么argv[0]现在是“--single”，这会造成奇怪的错误消息。我们在下面打印自己的错误消息。
	 */
	opterr = 0;
#endif

	/*
	 * 解析命令行选项。注意：保持与postmaster/postmaster.c同步（选项集不应冲突）及与main/main.c中的公共help()函数同步。
	 */
#ifdef FDD
	while ((fc_flag = getopt(fc_argc, fc_argv, "B:bc:C:D:M:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:v:W:-:")) != -1)
#else
	while ((fc_flag = getopt(fc_argc, fc_argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:v:W:-:")) != -1)
#endif
	{
		switch (fc_flag)
		{
			case 'B':
				SetConfigOption("shared_buffers", optarg, fc_ctx, fc_gucsource);
				break;

			case 'b':
				/* 用于二进制升级的未记录标志 */
				if (fc_secure)
					IsBinaryUpgrade = true;
				break;

			case 'C':
				/* 为与postmaster保持一致而被忽略 */
				break;
#ifdef FDD
			case 'M':
				g_tdepass = strdup(optarg);
				break;
#endif
			case 'D':
				if (fc_secure)
					userDoption = strdup(optarg);
				break;

			case 'd':
				set_debug_options(atoi(optarg), fc_ctx, fc_gucsource);
				break;

			case 'E':
				if (fc_secure)
					EchoQuery = true;
				break;

			case 'e':
				SetConfigOption("datestyle", "euro", fc_ctx, fc_gucsource);
				break;

			case 'F':
				SetConfigOption("fsync", "false", fc_ctx, fc_gucsource);
				break;

			case 'f':
				if (!set_plan_disabling_options(optarg, fc_ctx, fc_gucsource))
					fc_errs++;
				break;

			case 'h':
				SetConfigOption("listen_addresses", optarg, fc_ctx, fc_gucsource);
				break;

			case 'i':
				SetConfigOption("listen_addresses", "*", fc_ctx, fc_gucsource);
				break;

			case 'j':
				if (fc_secure)
					UseSemiNewlineNewline = true;
				break;

			case 'k':
				SetConfigOption("unix_socket_directories", optarg, fc_ctx, fc_gucsource);
				break;

			case 'l':
				SetConfigOption("ssl", "true", fc_ctx, fc_gucsource);
				break;

			case 'N':
				SetConfigOption("max_connections", optarg, fc_ctx, fc_gucsource);
				break;

			case 'n':
				/* 为与postmaster保持一致而被忽略 */
				break;

			case 'O':
				SetConfigOption("allow_system_table_mods", "true", fc_ctx, fc_gucsource);
				break;

			case 'P':
				SetConfigOption("ignore_system_indexes", "true", fc_ctx, fc_gucsource);
				break;

			case 'p':
				SetConfigOption("port", optarg, fc_ctx, fc_gucsource);
				break;

			case 'r':
				/* 将输出（stdout和stderr）发送到给定文件 */
				if (fc_secure)
					strlcpy(OutputFileName, optarg, MAXPGPATH);
				break;

			case 'S':
				SetConfigOption("work_mem", optarg, fc_ctx, fc_gucsource);
				break;

			case 's':
				SetConfigOption("log_statement_stats", "true", fc_ctx, fc_gucsource);
				break;

			case 'T':
				/* 为与postmaster保持一致而被忽略 */
				break;

			case 't':
				{
					const char *fc_tmp = get_stats_option_name(optarg);

					if (fc_tmp)
						SetConfigOption(fc_tmp, "true", fc_ctx, fc_gucsource);
					else
						fc_errs++;
					break;
				}

			case 'v':

				/*
				 * -v在正常操作中不再使用，因为
				 * FrontendProtocol在我们到达这里之前已经设置。我们保留
				 * 这个选项只是为了在独立操作中可能使用，以防将来我们支持使用正常的FE/BE协议与
				 * 独立后端。
				 */
				if (fc_secure)
					FrontendProtocol = (ProtocolVersion) atoi(optarg);
				break;

			case 'W':
				SetConfigOption("post_auth_delay", optarg, fc_ctx, fc_gucsource);
				break;

			case 'c':
			case '-':
				{
					char	   *fc_name,
							   *fc_value;

					ParseLongOption(optarg, &fc_name, &fc_value);
					if (!fc_value)
					{
						if (fc_flag == '-')
							ereport(ERROR,
									(errcode(ERRCODE_SYNTAX_ERROR),
									 errmsg("--%s requires a value",
											optarg)));
						else
							ereport(ERROR,
									(errcode(ERRCODE_SYNTAX_ERROR),
									 errmsg("-c %s requires a value",
											optarg)));
					}
					SetConfigOption(fc_name, fc_value, fc_ctx, fc_gucsource);
					free(fc_name);
					if (fc_value)
						free(fc_value);
					break;
				}

			default:
				fc_errs++;
				break;
		}

		if (fc_errs)
			break;
	}

	/*
	 * 可选的数据库名称只有在*dbname为NULL时才应该存在。
	 */
	if (!fc_errs && fc_dbname && *fc_dbname == NULL && fc_argc - optind >= 1)
		*fc_dbname = strdup(fc_argv[optind++]);

	if (fc_errs || fc_argc != optind)
	{
		if (fc_errs)
			optind--;			/* 对前一个参数提出抱怨 */

		/* 根据上下文略微不同地拼写错误消息 */
		if (IsUnderPostmaster)
			ereport(FATAL,
					errcode(ERRCODE_SYNTAX_ERROR),
					errmsg("invalid command-line argument for server process: %s", fc_argv[optind]),
					errhint("Try \"%s --help\" for more information.", progname));
		else
			ereport(FATAL,
					errcode(ERRCODE_SYNTAX_ERROR),
					errmsg("%s: invalid command-line argument: %s",
						   progname, fc_argv[optind]),
					errhint("Try \"%s --help\" for more information.", progname));
	}

	/*
	 * 重置getopt(3)库，以便在子进程中或当这个函数第二次用另一个数组调用时能够正确工作。
	 */
	optind = 1;
#ifdef HAVE_INT_OPTRESET
	optreset = 1;				/* 有些系统也需要这个 */
#endif
}


/*
 * PostgresSingleUserMain
 *     单用户模式的入口点。argc/argv 是将要使用的命令行
 *     参数。
 *
 * 执行单用户特定的设置，然后调用 PostgresMain() 来实际
 * 处理查询。单用户模式特定的设置应当在此进行，而不是在 
 * PostgresMain() 或 InitPostgres() 中，尽可能合理地做到这点。
 */
void PostgresSingleUserMain(int fc_argc, char *fc_argv[],
					   const char *fc_username)
{
	const char *fc_dbname = NULL;

	Assert(!IsUnderPostmaster);

	/* 初始化启动过程环境。 */
	InitStandaloneProcess(fc_argv[0]);

	/*
	 * 设置命令行选项的默认值。
	 */
	InitializeGUCOptions();

	/*
	 * 解析命令行选项。
	 */
	process_postgres_switches(fc_argc, fc_argv, PGC_POSTMASTER, &fc_dbname);

	/* 必须获得数据库名称，或者有默认值（用户名） */
	if (fc_dbname == NULL)
	{
		fc_dbname = fc_username;
		if (fc_dbname == NULL)
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("%s: no database nor user name specified",
							progname)));
	}

	/* 获取配置参数 */
	if (!SelectConfigFiles(userDoption, progname))
		proc_exit(1);

	/*
	 * 验证是否提供了看起来合理的 DataDir 并切换到它。
	 */
	checkDataDir();
	ChangeToDataDir();

	/*
	 * 为数据目录创建锁定文件。
	 */
	CreateDataDirLockFile(false);

	/* 读取控制文件 (错误检查并包含配置) */
	LocalProcessControlFile(false);

#ifdef FDD
	fdb_checkAndInitEncrypt();
#endif //FDD
	/*
	 * 处理任何应该在 postmaster 启动时预加载的库 
	 */
	process_shared_preload_libraries();

	/* 初始化 MaxBackends */
	InitializeMaxBackends();

	/*
	 * 给预加载库一个请求额外共享内存的机会。
	 */
	process_shmem_requests();

	/*
	 * 现在由于可加载模块有机会请求额外共享内存， 
	 * 确定任何运行时计算的 GUC 的值， 
	 * 该值依赖于所需的共享内存量。
	 */
	InitializeShmemGUCs();

	/*
	 * 现在已经加载模块，我们可以处理在 wal_consistency_checking GUC 中指定的 
	 * 所有自定义资源管理器。
	 */
	InitializeWalConsistencyChecking();

	CreateSharedMemoryAndSemaphores();

	/*
	 * 记住独立后端启动时间，大致在启动期间与 postmaster
	 * 相同的时间点进行。
	 */
	PgStartTime = GetCurrentTimestamp();

	/*
	 * 在共享内存中为每个后端创建一个 PGPROC 结构。我们必须
	 * 在使用 LWLocks 之前执行此操作。
	 */
	InitProcess();

	/*
	 * 现在基础设施已被充分初始化，PostgresMain()
	 * 可以完成其余工作。
	 */
	PostgresMain(fc_dbname, fc_username);
}


/* ----------------------------------------------------------------
 * PostgresMain
 *	   postgres 主循环 -- 所有后端，无论是交互式还是其他，
 *       在此循环
 *
 * dbname 是要连接的数据库的名称，username 是会话中使用的
 * PostgreSQL 用户名。
 *
 * 注意：单用户模式特定的设置应当尽可能地转移到 
 * PostgresSingleUserMain() 中进行。
 * ----------------------------------------------------------------
 */
void PostgresMain(const char *fc_dbname, const char *fc_username)
{
	sigjmp_buf	fc_local_sigjmp_buf;

	/* 这些必须是 volatile 以确保状态在 longjmp 之间被保留： */
	volatile bool fc_send_ready_for_query = true;
	volatile bool fc_idle_in_transaction_timeout_enabled = false;
	volatile bool fc_idle_session_timeout_enabled = false;
#ifdef FDD
	volatile bool		fc_transactionFailed = false;
#endif //FDD

	AssertArg(fc_dbname != NULL);
	AssertArg(fc_username != NULL);

	SetProcessingMode(InitProcessing);

	/*
	 * 设置信号处理程序。 (InitPostmasterChild 或 InitStandaloneProcess
	 * 已经设置了 BlockSig 并使其成为活动信号掩码。)
	 *
	 * 注意，postmaster 在分叉子进程之前阻止了所有信号，
	 * 因此不存在在设置处理程序之前可能收到信号的竞争条件。
	 *
	 * 同样注意：最好不要使用任何在 postmaster 中被 SIG_IGN 忽略的信号。
	 * 如果此类信号在我们能够将处理程序更改为非 SIG_IGN 之前到达，
	 * 它将被丢弃。相反，在 postmaster 中创建一个虚拟的处理程序以保留该信号。
	 * （当然，对于本地生成的信号，例如 SIGALRM 和 SIGPIPE， 
	 * 这不是问题。）
	 */
	if (am_walsender)
		WalSndSignals();
	else
	{
		pqsignal(SIGHUP, SignalHandlerForConfigReload);
		pqsignal(SIGINT, StatementCancelHandler);	/* 取消当前查询 */
		pqsignal(SIGTERM, die); /* 取消当前查询并退出 */

		/*
		 * 在 postmaster 子后端中，将 SignalHandlerForCrashExit
		 * 替换为 quickdie，以便我们可以告诉客户端我们正在退出。
		 *
		 * 在独立后端中，可以轻松从键盘生成 SIGQUIT，
		 * 而 SIGTERM 则不能，因此我们让这两个信号都执行 die()
		 * 而不是 quickdie()。
		 */
		if (IsUnderPostmaster)
			pqsignal(SIGQUIT, quickdie);	/* 硬崩溃时间 */
		else
			pqsignal(SIGQUIT, die); /* 取消当前查询并退出 */
		InitializeTimeouts();	/* 建立 SIGALRM 处理程序 */

		/*
		 * 忽略写入前端的失败。注意：如果前端关闭了
		 * 连接，我们会注意到，并在控制下次返回到外部循环时
		 * 干净地退出。这看起来比在不知道是什么操作的输出期间
		 * 强制退出要安全得多...
		 */
		pqsignal(SIGPIPE, SIG_IGN);
		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
		pqsignal(SIGUSR2, SIG_IGN);
		pqsignal(SIGFPE, FloatExceptionHandler);

		/*
		 * 重置一些被 postmaster 接受但不被后端接受的信号
		 */
		pqsignal(SIGCHLD, SIG_DFL); /* system() 在某些平台上需要这个 */
	}

	/* 早期初始化 */
	BaseInit();

	/* 我们需要在初始事务期间允许 SIGINT 等信号 */
	PG_SETMASK(&UnBlockSig);

	/*
	 * 一般初始化。
	 *
	 * 注意：如果你有意在此附近添加代码，请考虑将其放入
	 * InitPostgres() 中。特别是，任何涉及数据库访问的内容
	 * 应在那，而不是这里。
	 */
	InitPostgres(fc_dbname, InvalidOid,	/* 要连接的数据库 */
				 fc_username, InvalidOid,	/* 以哪个角色连接 */
				 !am_walsender, /* 尊重 session_preload_libraries？ */
				 false,			/* 不要忽略 datallowconn */
				 NULL);			/* 没有 out_dbname */

	/*
	 * 如果 PostmasterContext 仍然存在，回收这个空间；在
	 * InitPostgres 完成后，我们不再需要它。注意这不会破坏
	 * *MyProcPort，因为 ConnCreate() 使用 malloc() 分配了
	 * 那个空间... 否则我们需要先复制 Port 数据。此外，用户名等
	 * 衍生数据也不会丢失；见 ProcessStartupPacket()。
	 */
	if (PostmasterContext)
	{
		MemoryContextDelete(PostmasterContext);
		PostmasterContext = NULL;
	}

	SetProcessingMode(NormalProcessing);

	/*
	 * 现在所有 GUC 状态都已完全设置。如果合适，则将其报告给客户端。
	 */
	BeginReportingGUCOptions();

	/*
	 * 还设置处理程序以记录会话结束；我们必须等到现在才能
	 * 确保 Log_disconnections 具有最终值。
	 */
	if (IsUnderPostmaster && Log_disconnections)
		on_proc_exit(fc_log_disconnections, 0);

	pgstat_report_connect(MyDatabaseId);

	/* 执行特定于 WAL 发送进程的初始化。 */
	if (am_walsender)
		InitWalSender();

	/*
	 * 将此后端的取消信息发送到前端。
	 */
	if (whereToSendOutput == DestRemote)
	{
		StringInfoData fc_buf;

		pq_beginmessage(&fc_buf, 'K');
		pq_sendint32(&fc_buf, (int32) MyProcPid);
		pq_sendint32(&fc_buf, (int32) MyCancelKey);
		pq_endmessage(&fc_buf);
		/* 不需要刷新，因为 ReadyForQuery 将会处理它。 */
	}

	/* 独立情况下的欢迎横幅 */
	if (whereToSendOutput == DestDebug)
		printf("\nPostgreSQL stand-alone backend %s\n", PG_VERSION);

	/*
	 * 创建我们将在主循环中使用的内存上下文。
	 *
	 * MessageContext 在主循环的每次迭代中重置一次，即，在
	 * 处理来自客户端的每条命令消息完成时重置。
	 */
	MessageContext = AllocSetContextCreate(TopMemoryContext,
										   "MessageContext",
										   ALLOCSET_DEFAULT_SIZES);

	/*
	 * 创建用于 RowDescription 消息的内存上下文和缓冲区。由于
	 * SendRowDescriptionMessage() 通过 exec_describe_statement_message() 在
	 * 每条语句上频繁执行，我们不希望每次都分配一个单独的缓冲区。
	 */
	row_description_context = AllocSetContextCreate(TopMemoryContext,
													"RowDescriptionContext",
													ALLOCSET_DEFAULT_SIZES);
	MemoryContextSwitchTo(row_description_context);
	initStringInfo(&row_description_buf);
	MemoryContextSwitchTo(TopMemoryContext);

	/*
	 * POSTGRES 主处理循环从这里开始
	 *
	 * 如果遇到异常，处理将从这里恢复，因此我们中止当前事务并开始一个新事务。
	 *
	 * 你可能会想知道为什么这没有被编码为一个无限循环围绕
	 * PG_TRY 结构。原因是这是异常栈的底部，因此在 CATCH 部分
	 * 完全没有异常处理程序。如果将外部的 setjmp 始终保持激活，
	 * 我们至少有一些机会从错误中恢复。
	 * （如果因此进入无限循环，它很快会因为 elog.c 的内部状态栈溢出而被停止。）
	 *
	 * 请注意，我们使用 sigsetjmp(..., 1)，这样这个函数的信号掩码
	 * （即，UnBlockSig）在 longjmp 到这里时会被恢复。这在我们
	 * 从一个信号处理程序长跳转到这里的情况下是必不可少的。
	 * 这与 AbortTransaction() 中的解锁并不冗余，因为后者仅在
	 * 我们在事务内时被调用。
	 */
#ifdef FDD
	if(NULL != fdb_notify_event_hook)
	{
		fdb_notify_event_hook(FDB_EVENT_TYPE_LOGIN_OK, NULL, 0);
	}
#endif //FDD
	if (sigsetjmp(fc_local_sigjmp_buf, 1) != 0)
	{
		/*
		 * 注意：如果你倾向于在这个 if 块中添加更多代码，
		 * 考虑到高概率它应该在 AbortTransaction() 中。
		 * 在这里直接完成的唯一内容应该是
		 * 仅适用于外层错误恢复的内容，例如调整 FE/BE 协议状态。
		 */

		/* 由于未使用 PG_TRY，必须手动重置错误堆栈 */
		error_context_stack = NULL;

		/* 在清理期间防止中断 */
		HOLD_INTERRUPTS();

#ifdef FDD
		if(NULL != fdb_notify_event_hook && !fc_transactionFailed)
		{
			fc_transactionFailed = true;
			fdb_notify_event_hook(FDB_EVENT_TYPE_TRANSACTION_FAIL, NULL, 0);
		}
		fc_transactionFailed = false;
#endif //FDD
		/*
		 * 忘记任何挂起的 QueryCancel 请求，因为我们反正要返回
		 * 到空闲循环，并取消任何活动的超时请求。（未来我们可能想要
		 * 允许某些超时请求幸存，但至少在
		 * 我们到达这里是因为查询取消中断了 SIGALRM
		 * 中断处理程序的情况下，需要执行 reschedule_timeouts()。）
		 * 特别注意，我们必须清除语句和锁定超时指示符，以防止
		 * 在我们忘记超时取消的情况下，任何未来的普通查询取消
		 * 被错误报告为超时。
		 */
		disable_all_timeouts(false);	/* 首先执行以避免竞争条件 */
		QueryCancelPending = false;
		fc_idle_in_transaction_timeout_enabled = false;
		fc_idle_session_timeout_enabled = false;

		/* 不再从客户端读取。 */
		DoingCommandRead = false;

		/* 确保 libpq 处于良好状态 */
		pq_comm_reset();

		/* 向客户端和/或服务器日志报告错误 */
		EmitErrorReport();

		/*
		 * 确保在我们可能覆盖
		 * 它指向的存储之前重置 debug_query_string。
		 */
		debug_query_string = NULL;

		/*
		 * 中止当前事务以进行恢复。
		 */
		AbortCurrentTransaction();

		if (am_walsender)
			WalSndErrorCleanup();

		PortalErrorCleanup();

		/*
		 * 我们不能在 AbortTransaction() 内释放复制槽，因为我们
		 * 需要在获得槽的同时能够开始和中止事务。但是我们不需要
		 * 在顶层错误跨越之间保持它们，因此在这里释放是可以的。
		 * 还有一个 before_shmem_exit()
		 * 回调确保在 FATAL 错误上正确清理。
		 */
		if (MyReplicationSlot != NULL)
			ReplicationSlotRelease();

		/* 我们还想在错误时清理临时槽。 */
		ReplicationSlotCleanup();

		jit_reset_after_error();

		/*
		 * 现在返回到正常的顶级上下文并清除 ErrorContext，以便下次使用。
		 */
		MemoryContextSwitchTo(TopMemoryContext);
		FlushErrorState();

		/*
		 * 如果我们正在处理扩展查询协议消息，则开始
		 * 跳过直到下一个 Sync。这也会导致我们不发出
		 * ReadyForQuery（直到我们得到 Sync）。
		 */
		if (doing_extended_query_message)
			ignore_till_sync = true;

		/* 我们不再有打开的事务命令 */
		xact_started = false;

		/*
		 * 如果在从客户端读取消息时发生错误，我们可能
		 * 已经失去了对先前消息结束和下一条消息开始位置的跟踪。
		 * 即使我们已经从错误中恢复，我们也不能安全地从客户端
		 * 读取更多消息，因此我们在连接上不能做太多事情。
		 */
		if (pq_is_reading_msg())
			ereport(FATAL,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("terminating connection because protocol synchronization was lost")));

		/* 现在我们可以再次允许中断 */
		RESUME_INTERRUPTS();
	}

	/* 我们现在可以处理 ereport(ERROR) */
	PG_exception_stack = &fc_local_sigjmp_buf;

	if (!ignore_till_sync)
		fc_send_ready_for_query = true;	/* 最初，或在错误后 */


#ifdef FDD
	FDD_Update_EnableFbaseGis_Conf();
	FDB_Update_PasswordRule_Conf();
#endif

	/*
	 * 非错误查询循环在这里。
	 */

	for (;;)
	{
		int			fc_firstchar;
		StringInfoData fc_input_message;

		/*
		 * 在循环顶部，重置扩展查询消息标志，以便在
		 * “空闲”状态下遇到的任何错误不会引发跳过。
		 */
		doing_extended_query_message = false;

		/*
		 * 释放先前查询周期剩下的存储，并在清空的
		 * MessageContext 中创建一个新的查询输入缓冲区。
		 */
		MemoryContextSwitchTo(MessageContext);
		MemoryContextResetAndDeleteChildren(MessageContext);

		initStringInfo(&fc_input_message);

		/*
		 * 还可以考虑释放我们的目录快照（如果有），以便在
		 * 等待客户端时不会阻止全局 xmin 的推进。
		 */
		InvalidateCatalogSnapshotConditionally();

		
/*
		 * (1) 如果我们已达到空闲状态，通知前端我们已准备好
		 * 进行新的查询。
		 *
		 * 注意：这包括 fflush() 最后输出的内容。
		 *
		 * 这也是将收集的统计信息刷新到累积统计系统，并更新 PS 统计显示的好时机。我们
		 * 避免在消息循环中每次都执行，因为这会减慢批量消息的处理速度，并且我们不想
		 * 报告未提交的更新（这会让自动真空混淆）。如果我们不在
		 * 事务块中，通知处理程序也希望能调用一次。
		 *
		 * 此外，如果启用了空闲超时，请开始计时。
		 */
		if (fc_send_ready_for_query)
		{
			if (IsAbortedTransactionBlockState())
			{
				set_ps_display("idle in transaction (aborted)");
				pgstat_report_activity(STATE_IDLEINTRANSACTION_ABORTED, NULL);

				/* 启动空闲事务计时器 */
				if (IdleInTransactionSessionTimeout > 0)
				{
					fc_idle_in_transaction_timeout_enabled = true;
					enable_timeout_after(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
										 IdleInTransactionSessionTimeout);
				}
			}
			else if (IsTransactionOrTransactionBlock())
			{
				set_ps_display("idle in transaction");
				pgstat_report_activity(STATE_IDLEINTRANSACTION, NULL);

				/* 启动空闲事务计时器 */
				if (IdleInTransactionSessionTimeout > 0)
				{
					fc_idle_in_transaction_timeout_enabled = true;
					enable_timeout_after(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
										 IdleInTransactionSessionTimeout);
				}
			}
			else
			{
				long		fc_stats_timeout;

				/*
				 * 处理传入的通知（包括自我通知），如果有的话，向客户端发送相关消息。在这里执行
				 * 可以确保测试中的稳定行为：如果在刚结束的事务期间收到任何通知，它们
				 * 会在 ReadyForQuery 之前被客户端看到。
				 */
				if (notifyInterruptPending)
					ProcessNotifyInterrupt(false);

				/*
				 * 检查是否需要报告统计信息。如果 pgstat_report_stat()
				 * 决定现在报告待处理统计信息/锁定争用阻止报告
				 * 太早，它会告诉我们何时应该尝试再次报告统计信息（以便在连接长时间空闲时，
				 * 统计信息更新不会被不必要地延迟）。只有在我们没有正在进行的超时时，我们才会启用超时，
				 * 因为我们不会在下面禁用超时。enable_timeout_after() 需要确定
				 * 当前时间戳，这可能会对性能产生负面影响。这没关系，因为 pgstat_report_stat()
				 * 不会让我们比之前的调用提前醒来。
				 */
				fc_stats_timeout = pgstat_report_stat(false);
				if (fc_stats_timeout > 0)
				{
					if (!get_timeout_active(IDLE_STATS_UPDATE_TIMEOUT))
						enable_timeout_after(IDLE_STATS_UPDATE_TIMEOUT,
											 fc_stats_timeout);
				}
				else
				{
					/* 所有统计信息已刷新，无需超时 */
					if (get_timeout_active(IDLE_STATS_UPDATE_TIMEOUT))
						disable_timeout(IDLE_STATS_UPDATE_TIMEOUT, false);
				}

				set_ps_display("idle");
				pgstat_report_activity(STATE_IDLE, NULL);

				/* 启动空闲会话计时器 */
				if (IdleSessionTimeout > 0)
				{
					fc_idle_session_timeout_enabled = true;
					enable_timeout_after(IDLE_SESSION_TIMEOUT,
										 IdleSessionTimeout);
				}
			}

			/* 报告任何最近更改的 GUC 选项 */
			ReportChangedGUCOptions();

			ReadyForQuery(whereToSendOutput);
			fc_send_ready_for_query = false;
		}

		/*
		 * (2) 如果在等待客户端输入时接收到异步信号，则允许立即执行
		 * （这必须是有条件的，因为我们不想例如在执行 COPY FROM
		 * STDIN 时执行相同的操作。）
		 */
		DoingCommandRead = true;

		/*
		 * (3) 读取一个命令（此处循环阻塞）
		 */
		fc_firstchar = fc_ReadCommand(&fc_input_message);

		/*
		 * (4) 如果活动，关闭空闲事务和空闲会话超时。我们在步骤（5）之前执行此操作，以确保在步骤（5）中检测到任何最后时刻的超时。
		 *
		 * 最多只有一个超时会处于活动状态，因此不必担心将 timeout.c 的调用合并为一个。
		 */
		if (fc_idle_in_transaction_timeout_enabled)
		{
			disable_timeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT, false);
			fc_idle_in_transaction_timeout_enabled = false;
		}
		if (fc_idle_session_timeout_enabled)
		{
			disable_timeout(IDLE_SESSION_TIMEOUT, false);
			fc_idle_session_timeout_enabled = false;
		}

		/*
		 * (5) 再次禁用异步信号条件。
		 *
		 * 查询取消在没有进行中的查询时应该是无操作，因此如果在我们空闲时到达查询取消，仅重置 QueryCancelPending。当 DoingCommandRead 被设置时调用 ProcessInterrupts() 会实现这一效果，因此在重置 DoingCommandRead 之前检查中断。
		 */
		CHECK_FOR_INTERRUPTS();
		DoingCommandRead = false;

		/*
		 * (6) 检查在我们休眠时发生的其他有趣事件。
		 */
		if (ConfigReloadPending)
		{
			ConfigReloadPending = false;
			ProcessConfigFile(PGC_SIGHUP);
#ifdef FDD
			FDD_Update_EnableFbaseGis_Conf();
			FDB_Update_PasswordRule_Conf();
#endif
		}

		/*
		 * (7) 处理命令。但如果我们要跳过直到 Sync，则忽略它。
		 */
		if (ignore_till_sync && fc_firstchar != EOF)
			continue;

		switch (fc_firstchar)
		{
			case 'Q':			/* 简单查询 */
				{
					const char *fc_query_string;

					/* 设置 statement_timestamp() */
					SetCurrentStatementStartTimestamp();

					fc_query_string = pq_getmsgstring(&fc_input_message);
					pq_getmsgend(&fc_input_message);

					if (am_walsender)
					{
						if (!exec_replication_command(fc_query_string))
							fc_exec_simple_query(fc_query_string);
					}
					else
						fc_exec_simple_query(fc_query_string);

					fc_send_ready_for_query = true;
				}
				break;

			case 'P':			/* parse */
				{
					const char *fc_stmt_name;
					const char *fc_query_string;
					int			fc_numParams;
					Oid		   *fc_paramTypes = NULL;

					fc_forbidden_in_wal_sender(fc_firstchar);

					/* 设置 statement_timestamp() */
					SetCurrentStatementStartTimestamp();

					fc_stmt_name = pq_getmsgstring(&fc_input_message);
					fc_query_string = pq_getmsgstring(&fc_input_message);
					fc_numParams = pq_getmsgint(&fc_input_message, 2);
					if (fc_numParams > 0)
					{
						fc_paramTypes = (Oid *) palloc(fc_numParams * sizeof(Oid));
						for (int fc_i = 0; fc_i < fc_numParams; fc_i++)
							fc_paramTypes[fc_i] = pq_getmsgint(&fc_input_message, 4);
					}
					pq_getmsgend(&fc_input_message);

					fc_exec_parse_message(fc_query_string, fc_stmt_name,
									   fc_paramTypes, fc_numParams);
				}
				break;

			case 'B':			/* bind */
				fc_forbidden_in_wal_sender(fc_firstchar);

				/* 设置 statement_timestamp() */
				SetCurrentStatementStartTimestamp();

				/*
				 * 这个消息复杂到最好将字段提取放到外部
				 */
				fc_exec_bind_message(&fc_input_message);
				break;

			case 'E':			/* execute */
				{
					const char *fc_portal_name;
					int			fc_max_rows;

					fc_forbidden_in_wal_sender(fc_firstchar);

					/* 设置 statement_timestamp() */
					SetCurrentStatementStartTimestamp();

					fc_portal_name = pq_getmsgstring(&fc_input_message);
					fc_max_rows = pq_getmsgint(&fc_input_message, 4);
					pq_getmsgend(&fc_input_message);

					fc_exec_execute_message(fc_portal_name, fc_max_rows);
				}
				break;

			case 'F':			/* 快路径函数调用 */
				fc_forbidden_in_wal_sender(fc_firstchar);

				/* 设置 statement_timestamp() */
				SetCurrentStatementStartTimestamp();

				/* 向各种监控设施报告查询。 */
				pgstat_report_activity(STATE_FASTPATH, NULL);
				set_ps_display("<FASTPATH>");

				/* 为此函数调用启动事务 */
				fc_start_xact_command();

				/*
				 * 注意：此时我们可能在一个已中止的事务内。我们不能在完成读取函数调用消息之前抛出错误，因此 HandleFunctionRequest() 必须在这样做之后检查它。小心不要执行任何假设我们在有效事务内的操作。
				 */

				/* 切换回消息上下文 */
				MemoryContextSwitchTo(MessageContext);

				HandleFunctionRequest(&fc_input_message);

				/* 提交函数调用事务 */
				fc_finish_xact_command();

				fc_send_ready_for_query = true;
				break;

			case 'C':			/* close */
				{
					int			fc_close_type;
					const char *fc_close_target;

					fc_forbidden_in_wal_sender(fc_firstchar);

					fc_close_type = pq_getmsgbyte(&fc_input_message);
					fc_close_target = pq_getmsgstring(&fc_input_message);
					pq_getmsgend(&fc_input_message);

					switch (fc_close_type)
					{
						case 'S':
							if (fc_close_target[0] != '\0')
								DropPreparedStatement(fc_close_target, false);
							else
							{
								/* 特殊情况处理未命名语句 */
								fc_drop_unnamed_stmt();
							}
							break;
						case 'P':
							{
								Portal		fc_portal;

								fc_portal = GetPortalByName(fc_close_target);
								if (PortalIsValid(fc_portal))
									PortalDrop(fc_portal, false);
							}
							break;
						default:
							ereport(ERROR,
									(errcode(ERRCODE_PROTOCOL_VIOLATION),
									 errmsg("invalid CLOSE message subtype %d",
											fc_close_type)));
							break;
					}

					if (whereToSendOutput == DestRemote)
						pq_putemptymessage('3');	/* CloseComplete */
				}
				break;

			case 'D':			/* describe */
				{
					int			fc_describe_type;
					const char *fc_describe_target;

					fc_forbidden_in_wal_sender(fc_firstchar);

					/* 设置 statement_timestamp()（事务所需） */
					SetCurrentStatementStartTimestamp();

					fc_describe_type = pq_getmsgbyte(&fc_input_message);
					fc_describe_target = pq_getmsgstring(&fc_input_message);
					pq_getmsgend(&fc_input_message);

					switch (fc_describe_type)
					{
						case 'S':
							fc_exec_describe_statement_message(fc_describe_target);
							break;
						case 'P':
							fc_exec_describe_portal_message(fc_describe_target);
							break;
						default:
							ereport(ERROR,
									(errcode(ERRCODE_PROTOCOL_VIOLATION),
									 errmsg("invalid DESCRIBE message subtype %d",
											fc_describe_type)));
							break;
					}
				}
				break;

			case 'H':			/* 刷新 */
				pq_getmsgend(&fc_input_message);
				if (whereToSendOutput == DestRemote)
					pq_flush();
				break;

			case 'S':			/* sync */
				pq_getmsgend(&fc_input_message);
				fc_finish_xact_command();
				fc_send_ready_for_query = true;
				break;

				/*
				 * 'X' 表示前端正在关闭套接字。EOF 表示意外丢失前端连接。无论哪种情况，执行正常的关闭。
				 */
			case EOF:

				/* 用于累积统计系统 */
				pgStatSessionEndCause = DISCONNECT_CLIENT_EOF;

				/* FALLTHROUGH */

			case 'X':

				/*
				 * 重置 whereToSendOutput 以防止 ereport 尝试向客户端发送更多消息。
				 */
				if (whereToSendOutput == DestRemote)
					whereToSendOutput = DestNone;

				/*
				 * 注意：如果您想在这里添加更多代码，请不要！您所想到的任何代码都应该设置为 on_proc_exit 或 on_shmem_exit 回调。否则它将无法在其他后端关闭场景中被调用。
				 */
				proc_exit(0);

			case 'd':			/* 复制数据 */
			case 'c':			/* 复制完成 */
			case 'f':			/* 复制失败 */

				/*
				 * 接受但忽略这些消息，按照协议规范；我们可能是由于 COPY 失败而到达这里，并且前端仍在发送数据。
				 */
				break;

			default:
				ereport(FATAL,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("invalid frontend message type %d",
								fc_firstchar)));
		}
	}							/* 输入读取循环结束 */
}

/*
 * 如果我们是 WAL 发送者进程，则抛出错误。
 *
 * 这用于禁止 WAL 发送者进程中除简单查询协议消息以外的任何其他内容。'firstchar' 指定收到的被禁止消息的类型，并用于构造错误消息。
 */
static void fc_forbidden_in_wal_sender(char fc_firstchar)
{
	if (am_walsender)
	{
		if (fc_firstchar == 'F')
			ereport(ERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("fastpath function calls not supported in a replication connection")));
		else
			ereport(ERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("extended query protocol not supported in a replication connection")));
	}
}


/*
 * 获取平台堆栈深度限制（以字节为单位）
 *
 * 如果未知则返回 -1
 */
long get_stack_depth_rlimit(void)
{
#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_STACK)
	static long fc_val = 0;

	/* 这在进程启动后不会更改，因此只检查一次 */
	if (fc_val == 0)
	{
		struct rlimit fc_rlim;

		if (getrlimit(RLIMIT_STACK, &fc_rlim) < 0)
			fc_val = -1;
		else if (fc_rlim.rlim_cur == RLIM_INFINITY)
			fc_val = LONG_MAX;
		/* rlim_cur 可能是无符号类型，因此检查溢出 */
		else if (fc_rlim.rlim_cur >= LONG_MAX)
			fc_val = LONG_MAX;
		else
			fc_val = fc_rlim.rlim_cur;
	}
	return fc_val;
#else							/* 不支持 getrlimit */
#if defined(WIN32) || defined(__CYGWIN__)
	/* 在 Windows 上，我们在 src/backend/Makefile 中设置后端堆栈大小 */
	return WIN32_STACK_RLIMIT;
#else							/* 不是 Windows...放弃 */
	return -1;
#endif
#endif
}


static struct rusage Save_r;
static struct timeval Save_t;

void ResetUsage(void)
{
	getrusage(RUSAGE_SELF, &Save_r);
	gettimeofday(&Save_t, NULL);
}

void ShowUsage(const char *fc_title)
{
	StringInfoData fc_str;
	struct timeval fc_user,
				fc_sys;
	struct timeval fc_elapse_t;
	struct rusage fc_r;

	getrusage(RUSAGE_SELF, &fc_r);
	gettimeofday(&fc_elapse_t, NULL);
	memcpy((char *) &fc_user, (char *) &fc_r.ru_utime, sizeof(fc_user));
	memcpy((char *) &fc_sys, (char *) &fc_r.ru_stime, sizeof(fc_sys));
	if (fc_elapse_t.tv_usec < Save_t.tv_usec)
	{
		fc_elapse_t.tv_sec--;
		fc_elapse_t.tv_usec += 1000000;
	}
	if (fc_r.ru_utime.tv_usec < Save_r.ru_utime.tv_usec)
	{
		fc_r.ru_utime.tv_sec--;
		fc_r.ru_utime.tv_usec += 1000000;
	}
	if (fc_r.ru_stime.tv_usec < Save_r.ru_stime.tv_usec)
	{
		fc_r.ru_stime.tv_sec--;
		fc_r.ru_stime.tv_usec += 1000000;
	}

	/*
	 * 我们在这里不显示的唯一统计数据是 ixrss、idrss、isrss。解释它们需要一些工作，并且大多数平台不会填写它们。
	 */
	initStringInfo(&fc_str);

	appendStringInfoString(&fc_str, "! system usage stats:\n");
	appendStringInfo(&fc_str,
					 "!\t%ld.%06ld s user, %ld.%06ld s system, %ld.%06ld s elapsed\n",
					 (long) (fc_r.ru_utime.tv_sec - Save_r.ru_utime.tv_sec),
					 (long) (fc_r.ru_utime.tv_usec - Save_r.ru_utime.tv_usec),
					 (long) (fc_r.ru_stime.tv_sec - Save_r.ru_stime.tv_sec),
					 (long) (fc_r.ru_stime.tv_usec - Save_r.ru_stime.tv_usec),
					 (long) (fc_elapse_t.tv_sec - Save_t.tv_sec),
					 (long) (fc_elapse_t.tv_usec - Save_t.tv_usec));
	appendStringInfo(&fc_str,
					 "!\t[%ld.%06ld s user, %ld.%06ld s system total]\n",
					 (long) fc_user.tv_sec,
					 (long) fc_user.tv_usec,
					 (long) fc_sys.tv_sec,
					 (long) fc_sys.tv_usec);
#if defined(HAVE_GETRUSAGE)
	appendStringInfo(&fc_str,
					 "!\t%ld kB max resident size\n",
#if defined(__darwin__)
	/* 在 macOS 上以字节为单位 */
					 r.ru_maxrss / 1024
#else
	/* 在大多数其他平台上以千字节为单位 */
					 fc_r.ru_maxrss
#endif
		);
	appendStringInfo(&fc_str,
					 "!\t%ld/%ld [%ld/%ld] filesystem blocks in/out\n",
					 fc_r.ru_inblock - Save_r.ru_inblock,
	/* 他们只在 dec 喝咖啡 */
					 fc_r.ru_oublock - Save_r.ru_oublock,
					 fc_r.ru_inblock, fc_r.ru_oublock);
	appendStringInfo(&fc_str,
					 "!\t%ld/%ld [%ld/%ld] page faults/reclaims, %ld [%ld] swaps\n",
					 fc_r.ru_majflt - Save_r.ru_majflt,
					 fc_r.ru_minflt - Save_r.ru_minflt,
					 fc_r.ru_majflt, fc_r.ru_minflt,
					 fc_r.ru_nswap - Save_r.ru_nswap,
					 fc_r.ru_nswap);
	appendStringInfo(&fc_str,
					 "!\t%ld [%ld] signals rcvd, %ld/%ld [%ld/%ld] messages rcvd/sent\n",
					 fc_r.ru_nsignals - Save_r.ru_nsignals,
					 fc_r.ru_nsignals,
					 fc_r.ru_msgrcv - Save_r.ru_msgrcv,
					 fc_r.ru_msgsnd - Save_r.ru_msgsnd,
					 fc_r.ru_msgrcv, fc_r.ru_msgsnd);
	appendStringInfo(&fc_str,
					 "!\t%ld/%ld [%ld/%ld] voluntary/involuntary context switches\n",
					 fc_r.ru_nvcsw - Save_r.ru_nvcsw,
					 fc_r.ru_nivcsw - Save_r.ru_nivcsw,
					 fc_r.ru_nvcsw, fc_r.ru_nivcsw);
#endif							/* HAVE_GETRUSAGE */

	/* 移除结尾的换行符 */
	if (fc_str.data[fc_str.len - 1] == '\n')
		fc_str.data[--fc_str.len] = '\0';

	ereport(LOG,
			(errmsg_internal("%s", fc_title),
			 errdetail_internal("%s", fc_str.data)));

	pfree(fc_str.data);
}

/*
 * on_proc_exit 处理程序记录会话结束
 */
static void fc_log_disconnections(int fc_code, Datum fc_arg)
{
	Port	   *fc_port = MyProcPort;
	long		fc_secs;
	int			fc_usecs;
	int			fc_msecs;
	int			fc_hours,
				fc_minutes,
				fc_seconds;

	TimestampDifference(MyStartTimestamp,
						GetCurrentTimestamp(),
						&fc_secs, &fc_usecs);
	fc_msecs = fc_usecs / 1000;

	fc_hours = fc_secs / SECS_PER_HOUR;
	fc_secs %= SECS_PER_HOUR;
	fc_minutes = fc_secs / SECS_PER_MINUTE;
	fc_seconds = fc_secs % SECS_PER_MINUTE;

	ereport(LOG,
			(errmsg("disconnection: session time: %d:%02d:%02d.%03d "
					"user=%s database=%s host=%s%s%s",
					fc_hours, fc_minutes, fc_seconds, fc_msecs,
					fc_port->user_name, fc_port->database_name, fc_port->remote_host,
					fc_port->remote_port[0] ? " port=" : "", fc_port->remote_port)));
}

/*
 * 启动语句超时计时器（如果启用）。
 *
 * 如果已经有一个超时在运行，不要重新启动计时器。这样可以在超时的准确性和启动超时的成本之间进行折衷。
 */
static void fc_enable_statement_timeout(void)
{
	/* 必须在一个事务中 */
	Assert(xact_started);

	if (StatementTimeout > 0)
	{
		if (!get_timeout_active(STATEMENT_TIMEOUT))
			enable_timeout_after(STATEMENT_TIMEOUT, StatementTimeout);
	}
	else
	{
		if (get_timeout_active(STATEMENT_TIMEOUT))
			disable_timeout(STATEMENT_TIMEOUT, false);
	}
}

/*
 * 禁用正在活动的语句超时（如果活动）。
 */
static void fc_disable_statement_timeout(void)
{
	if (get_timeout_active(STATEMENT_TIMEOUT))
		disable_timeout(STATEMENT_TIMEOUT, false);
}
